[JZOJ5977] 【清華2019冬令營模擬12.15】堆
題目
其中
題目大意
讓你維護一個堆。支援一下操作:
在某個點的下面加上另一個點,然後進行上浮操作。
詢問某一點的權值。
思考歷程
一眼看這題,誒,不就是那道中學生資料結構題嗎?
直接樹鏈剖分,然後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真是一個好東西……
……