對於每個點維護兩棵線段樹$T1[x],T2[x]$:
$T1[x]$維護$x$子樹內,深度在$[l,r]$內的點數,同種顏色有多個的話,保留深度最小的那個。
$T2[x]$維護$x$子樹內每種顏色的最小深度。
從底向上合併線段樹,先合併$T1$,然後合併$T2$的時候,發現有重複點,那麼在$T1$裡刪去深度大的那個,查詢直接在$T1$裡區間求和即可。
時間複雜度$O((n+m)\log n)$。
#include<cstdio> const int N=100010,M=10000000; int Case,n,m,i,x,y,ans,a[N],f[N],d[N],T1[N],T2[N],tot,l[M],r[M],v[M]; int ins(int x,int a,int b,int c,int p){ int y=++tot;v[y]=v[x]+p; if(a==b)return y; int mid=(a+b)>>1; if(c<=mid)l[y]=ins(l[x],a,mid,c,p),r[y]=r[x]; else l[y]=l[x],r[y]=ins(r[x],mid+1,b,c,p); return y; } int merge1(int x,int y,int a,int b){ if(!x||!y)return x+y; int z=++tot; v[z]=v[x]+v[y]; if(a==b)return z; int mid=(a+b)>>1; l[z]=merge1(l[x],l[y],a,mid); r[z]=merge1(r[x],r[y],mid+1,b); return z; } int merge2(int x,int y,int a,int b,int p){ if(!x||!y)return x+y; int z=++tot; if(a==b){ if(v[x]<v[y])v[z]=v[x],T1[p]=ins(T1[p],1,n,v[y],-1); else v[z]=v[y],T1[p]=ins(T1[p],1,n,v[x],-1); return z; } int mid=(a+b)>>1; l[z]=merge2(l[x],l[y],a,mid,p); r[z]=merge2(r[x],r[y],mid+1,b,p); return z; } int ask(int x,int a,int b,int d){ if(b<=d)return v[x]; int mid=(a+b)>>1,t=ask(l[x],a,mid,d); if(d>mid)t+=ask(r[x],mid+1,b,d); return t; } int main(){ scanf("%d",&Case); while(Case--){ scanf("%d%d",&n,&m); for(i=1;i<=n;i++)scanf("%d",&a[i]); for(i=2;i<=n;i++)scanf("%d",&f[i]); for(i=1;i<=n;i++)d[i]=d[f[i]]+1; for(i=1;i<=n;i++){ T1[i]=ins(0,1,n,d[i],1); T2[i]=ins(0,1,n,a[i],d[i]); } for(i=n;i>1;i--){ T1[f[i]]=merge1(T1[f[i]],T1[i],1,n); T2[f[i]]=merge2(T2[f[i]],T2[i],1,n,f[i]); } while(m--){ scanf("%d%d",&x,&y);x^=ans,y^=ans; y+=d[x]; if(y>n)y=n; printf("%d\n",ans=ask(T1[x],1,n,y)); } ans=tot=0; } return 0; }