1. 程式人生 > >[JZOJ5977] 【清華2019冬令營模擬12.15】堆

[JZOJ5977] 【清華2019冬令營模擬12.15】堆

題目

在這裡插入圖片描述
其中 n , q 500000 n,q\leq 500000

題目大意

讓你維護一個堆。支援一下操作:
在某個點的下面加上另一個點,然後進行上浮操作。
詢問某一點的權值。


思考歷程

一眼看這題,誒,不就是那道中學生資料結構題嗎?
直接樹鏈剖分,然後splay一波搞定!
思想還是很簡單的!
但是感覺有點長……


正解

上面的這個解法算是一個正解吧。
但是我還是沒打,因為程式碼可能很長……(想一想,又樹鏈剖分,又splay的有點麻煩)

然後這題LCT也可以做!就是LCT和一個splay!由於LCT本來就是用splay來實現的,所以,在打板的時候還比較容易,只不過……
先不要說。現在說一說解法。
我們知道,上浮操作其實就是輪換操作。但是,如果我們直接在LCT中輪換,那麼我們會將點的位置改變,不只是權值的改變!
所以說,我們要再用一個splay來維護一下鏈上點的權值。對於LCT上的每一條鏈(也就是LCT中的每一個splay),另外維護一個以權值為關鍵字的splay。這兩個splay其中的順序是一一對應的。當我們要輪換的時候,只需要輪換那個splay中的值。在查詢的時候查與其對應位置的值就好了。
不過再想想,程式碼還是有點複雜的,雖然比較簡單。我們再進行LCT操作的時候,我們還要維護一下那些splay,所以要加一些東西!當然,在巨集觀的意義上,這個還是比較好理解的。

然後還有一個比較簡單的做法:離線,樹鏈剖分和權值線段樹!
這個似乎比較好打很多……在這裡理清一下思路。
我們先將整棵樹建出來(當然,是樹的最後形態),然後樹鏈剖分剖成許多條重鏈!
對於每一條重鏈,我們分別維護一個權值線段樹。
在我們進行操作的時候,如果是詢問,那就直接通過它在重鏈中的位置,在權值線段樹中把它給找出來。
如果是插入一個點,那就將其加入權值線段樹中(之前是無限大),然後和重鏈頂端的父親上面的值對比一下,如果比它小,那就進行替換,然後繼續往上跳。
其實不一定要使用權值線段樹,平衡樹也是一個不錯的選擇……

總感覺這題的正解有好多……


程式碼

using namespace
std; #include <cstdio> #include <cstring> #include <algorithm> #define N 1000000 #define Q 500000 #define MAX 1000001 int n,nn,q; struct EDGE{ int to; EDGE *las; } e[N+1]; int ne; EDGE *last[N+1]; inline void link(int u,int v){ e[++ne]={v,last[u]}; last[u]=e+ne; } int a[N+1]; struct Oper{ bool op; int x; } o[Q+1]; int fa[N+1],siz[N+1],dep[N+1],hs[N+1]; void init1(int); int top[N+1]; void init2(int,int); struct Segment_Tree{ int l,r; int sum; } seg[20000001]; int cnt; void add(int,int,int,int,int); int kth(int,int,int,int); int tos[N+1];//tos[i]表示i所在的重鏈對應的線段樹根節點的編號 inline void insert(int); int main(){ freopen("heap.in","r",stdin); freopen("heap.out","w",stdout); scanf("%d%d",&n,&q); for (int i=1;i<=n;++i){ scanf("%d%d",&fa[i],&a[i]); if (fa[i]) link(fa[i],i); } nn=n; for (int i=1;i<=q;++i){ int op; scanf("%d",&op); if (op==1){ ++nn; scanf("%d%d",&fa[nn],&a[nn]); link(fa[nn],nn); o[i]={0,nn}; } else{ int x; scanf("%d",&x); o[i]={1,x}; } } init1(1); init2(1,1); for (int i=1;i<=nn;++i) if (top[i]==i){ tos[i]=++cnt; seg[cnt]={0,0,0}; } for (int i=1;i<=nn;++i) tos[i]=tos[top[i]]; for (int i=1;i<=n;++i) add(tos[i],1,MAX,a[i],1); for (int i=1;i<=q;++i) if (o[i].op==0) insert(o[i].x); else printf("%d\n",kth(tos[o[i].x],1,MAX,dep[o[i].x]-dep[top[o[i].x]]+1)); return 0; } void init1(int x){ dep[x]=dep[fa[x]]+1; siz[x]=1; for (EDGE *ei=last[x];ei;ei=ei->las){ init1(ei->to); siz[x]+=siz[ei->to]; if (siz[ei->to]>siz[hs[x]]) hs[x]=ei->to; } } void init2(int x,int t){ top[x]=t; if (hs[x]) init2(hs[x],t); for (EDGE *ei=last[x];ei;ei=ei->las) if (ei->to!=hs[x]) init2(ei->to,ei->to); } void add(int k,int l,int r,int x,int c){ seg[k].sum+=c; if (l==r) return; int mid=l+r>>1; if (x<=mid){ if (!seg[k].l) seg[k].l=++cnt; add(seg[k].l,l,mid,x,c); } else{ if (!seg[k].r) seg[k].r=++cnt; add(seg[k].r,mid+1,r,x,c); } } int kth(int k,int l,int r,int x){ if (l==r) return l; int mid=l+r>>1; if (x<=seg[seg[k].l].sum) return kth(seg[k].l,l,mid,x); return kth(seg[k].r,mid+1,r,x-seg[seg[k].l].sum); } inline void insert(int x){ add(tos[x],1,MAX,a[x],1); int v=a[x]; while (fa[top[x]]){ int y=fa[top[x]],mn=kth(tos[y],1,MAX,dep[y]-dep[top[y]]+1);//找出重鏈的父親的值 if (v<mn){ //交換 add(tos[x],1,MAX,v,-1); add(tos[x],1,MAX,mn,1); add(tos[y],1,MAX,mn,-1); add(tos[y],1,MAX,v,1); x=y; } else break; } }

總結

樹鏈剖分真是一個好東西……
LCT真是一個好東西……
……