BZOJ 3720 [洛谷P2137] : Gty的妹子樹
Description
我曾在絃歌之中聽過你,
檀板聲碎,半出摺子戲。
舞榭歌臺被風吹去,
歲月深處尚有餘音一縷……
Gty神(xian)犇(chong)從來不缺妹子……
他來到了一棵妹子樹下,發現每個妹子有一個美麗度……
由於Gty很哲♂學,他只對美麗度大於某個值的妹子感興趣。
他想知道某個子樹中美麗度大於k的妹子個數。
某個妹子的美麗度可能發生變化……
樹上可能會出現一隻新的妹子……
維護一棵初始有n個節點的有根樹(根節點為1),樹上節點編號為1-n,每個點有一個權值wi。
支援以下操作:
0 u x 詢問以u為根的子樹中,嚴格大於x的值的個數。(u^=lastans,x^=lastans)
1 u x 把u節點的權值改成x。(u^=lastans,x^=lastans)
2 u x 新增一個編號為"當前樹中節點數+1"的節點,其父節點為u,其權值為x。(u^=lastans,x^=lastans)
最開始時lastans=0。
Input
輸入第一行包括一個正整數n(1<=n<=30000),代表樹上的初始節點數。
接下來n-1行,每行2個整數u,v,為樹上的一條無向邊。
任何時刻,樹上的任何權值大於等於0,且兩兩不同。
接下來1行,包括n個整數wi,表示初始時每個節點的權值。
接下來1行,包括1個整數m(1<=m<=30000),表示操作總數。
接下來m行,每行包括三個整數 op,u,v:
op,u,v的含義見題目描述。
保證題目涉及的所有數在int內。
Output
對每個op=0,輸出一行,包括一個整數,意義見題目描述。
Sample Input
2
1 2
10 20
1
0 1 5
Sample Output
2
Solution
-
大家多用的是樹分塊的方法,這裡我用的歸併樹+定期重構。
-
具體怎樣呢?關鍵是我們考慮每個修改對之後詢問的影響。
-
如果沒有修改(靜態詢問),我們對dfs序建歸併樹,直接區間查詢即可。
-
(歸併樹就是一種線段樹,區間記憶體的是這個區間權值排序後的序列,查詢時在上面二分)
-
有了修改,我們就要判斷修改對詢問有影響,其中修改點要在詢問點的子樹內。
-
如何判斷是否在子樹內:倍增跳。加點時處理出其 級父親。
-
於是我們得到這樣一個演算法:我們在歸併樹中查詢後,針對若干修改操作暴力判斷影響。
-
那我們就可以定期重構啦!
-
如果我們在查詢之前的修改不超過 次時,就在歸併樹上查詢後暴力掃描修改計算貢獻;
-
如果修改超過了 次時,我們只要根據修改重建一下歸併樹就可以清除掉這些修改,
-
可以發現歸併樹的重建不會超過 次。
-
那麼我們來分析一下複雜度:(假設 同階)
-
每次掃描修改算貢獻,修改最多 個,每次倍增判是否在子樹要 ,複雜度為 。
-
每次重建歸併樹要 ,最多重建 次,故複雜度同是 。
-
於是這題就解決了,總時間複雜度 。
-
有一些細節要注意:
-
由於重建歸併樹常數比較大,我們可以多幾次修改再重建一次,比如說 。
-
還有就是打線段樹詢問時: ,由於加點時 會增加,但帶進詢問時仍然要是之前的 ,不然就不對了,重構時再把 的 改成新的。
Code
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<vector>
#include<cctype>
using namespace std;
const int N=60005;
struct data
{
int op,x,y,z;
}q[N>>1];
int n,tot,num,cnt,qx,qy,qz,last,lim;
int first[N],nex[N<<1],en[N<<1];
int w[N],dfn[N],size[N],id[N],dep[N],fa[N][16];
vector<int>ss[N<<2];
inline int read()
{
int X=0,w=0; char ch=0;
while(!isdigit(ch)) w|=ch=='-',ch=getchar();
while(isdigit(ch)) X=(X<<3)+(X<<1)+(ch^48),ch=getchar();
return w?-X:X;
}
void write(int x)
{
if(x>9) write(x/10);
putchar(x%10+'0');
}
inline void insert(int x,int y)
{
nex[++tot]=first[x];
first[x]=tot;
en[tot]=y;
}
void dfs(int x)
{
dfn[x]=++cnt;
id[cnt]=x;
size[x]=1;
dep[x]=dep[fa[x][0]]+1;
for(int i=first[x];i;i=nex[i])
if(en[i]^fa[x][0])
{
fa[en[i]][0]=x;
dfs(en[i]);
size[x]+=size[en[i]];
}
}
void make(int v,int l,int r)
{
ss[v].clear();
if(l==r)
{
ss[v].push_back(w[id[l]]);
return;
}
int mid=l+r>>1,ls=v<<1,rs=ls|1;
make(ls,l,mid);
make(rs,mid+1,r);
int i=0,ni=ss[ls].size()-1;
int j=0,nj=ss[rs].size()-1;
while(i<=ni && j<=nj) ss[v].push_back(ss[ls][i]<ss[rs][j]?ss[ls][i++]:ss[rs][j++]);
while(i<=ni) ss[v].push_back(ss[ls][i++]);
while(j<=nj) ss[v].push_back(ss[rs][j++]);
}
int find(int v,int l,int r)
{
if(qx<=l && r<=qy) return ss[v].size()-(upper_bound(ss[v].begin(),ss[v].end()--,qz)-ss[v].begin());
int mid=l+r>>1,s=0;
if(qx<=mid) s=find(v<<1,l,mid);
if(qy>mid) s+=find(v<<1|1,mid+1,r);
return s;
}
void dfs1(int x)
{
size[x]=1;
dfn[x]=++cnt;
id[cnt]=x;
for(int i=first[x];i;i=nex[i])
if(en[i]^fa[x][0])
{
dfs(en[i]);
size[x]+=size[en[i]];
}
}
inline void rebuild()
{
cnt=num=0;
dfs1(1);
make(1,1,n);
cnt=n;
}
inline bool belong(int x,int y)
{
if(dep[x]<dep[y]) return false;
for(int i=log2(dep[x]);i>=0;i--)
if(dep[fa[x][i]]>=dep[y]) x=fa[x][i];
return x==y;
}
int main()
{
n=read();
for(int i=1;i<n;i++)
{
int x=read(),y=read();
insert(x,y);
insert(y,x);
}
for(int i=1;i<=n;i++) w[i]=read();
dfs(1);
cnt=n;
for(int j=1;j<16;j++)
for(int i=1;i<=n;i++)
fa[i][j]=fa[fa[i][j-1]][j-1];
make(1,1,n);
int m=read();
lim=ceil(sqrt(m)*5);
while(m--)
{
int op=read(),x=read()^last,y=read()^last;
if(op==0)
{
if(x<=cnt)
{
qx=dfn[x],qy=dfn[x]+size[x]-1,qz=y;
last=find(1,1,cnt);
}else last=0;
for(int i=1;i<=num;i++)
if(q[i].op==1)
{
if((q[i].y<y)^(q[i].z<y) && belong(q[i].x,x)) last+=q[i].z>y?1:-1;
}else
{
if(q[i].z>y && belong(q[i].x,x)) last++;
}
write(last),putchar('\n');
}else
if(op==1)
{
q[++num]=(data){1,x,w[x],y};
w[x]=y;
if(num==lim) rebuild();
}else
{
q[++num]=(data){2,++n,x,y};
insert(x,n);
insert(n,x);
fa[n][0]=x;
dep[n]=dep[x]+1;
w[n]=y;
for(int i=1;i<16;i++) fa[n][i]=fa[fa[n][i-1]][i-1];
if(num==lim) rebuild();
}
}
return 0;
}