BZOJ4940 YNOI2016 这是我自己的发明
题目大意
给定一棵树,支持换根,求在$x$子树中选一个点,在$y$子树中选一个点,点权相同的方案数。
题解
由于限制较多,点权无修改,不强制在线,又看到是YNOI,所以似乎只能用莫队乱搞。
暂时不考虑换根。求出原树的$DFS$序,每次即询问两段区间$[L_1,R_1],[L_2,R_2]$之间的答案。
记$Ans(L,R)$表示区间$[1,L],[1,R]$之间的答案,用容斥原理得出$[L_1,R_1][L_2,R_2]$之间的答案$=Ans(R_1,R_2)+Ans(L_1-1,L_2-1)-Ans(L_1-1,R_2)-Ans(R_1,L_2-1)$
对于$Ans(x,y)$,我们先直接钦定$x<y$(显然不影响答案)只需要维护两个数组存离散化之后的点权的数量,就可以$O(1)$维护$Ans(x\pm 1,y\pm 1)$,所以直接莫队就好。
原始根为$1$,设当前根为$Root$,当前节点为$x$,则考虑换根对$x$子树对应区间的影响如下。
若$Root=x$则对应区间为全部$n$个点。
若$Root$不在$x$子树内,则$x$区间不变。
若$Root$在$x$子树内,则设$k$表示$Root$在以$x$的儿子$k$为根的子树内,则区间为除去$k$及其子树的剩余部分。
显然$x$子树对应区间段数最多为一个前缀加一个后缀,可以直接倍长原序列,将前缀向后平移$n$位与后缀接起来即可。
复杂度为$O(4Q\log Q+n\sqrt N)$
#include<algorithm> #include<iostream> #include<cstring> #include<cstdio> #include<cmath> #define LL long long #define M 200020 using namespace std; const int BS=(1<<19); char Buffer[BS],OT[BS],*OS=OT,*head,*tail,ss[20]; const char *fin=OT+BS-1; int Top; char Getchar(){if(head==tail){tail=(head=Buffer)+fread(Buffer,1,BS,stdin);} return *head++;} void flush(){fwrite(OT,1,OS-OT,stdout);} void Putchar(char c){*OS++ =c;if(OS==fin) flush(),OS=OT;} void write(LL x){ if(!x){Putchar('0');return;} while(x) ss[++Top]=x%10,x/=10; while(Top) Putchar(ss[Top]+'0'),--Top; } int read(){ int nm=0,fh=1; char cw=Getchar(); for(;!isdigit(cw);cw=Getchar()) if(cw=='-') fh=-fh; for(;isdigit(cw);cw=Getchar()) nm=nm*10+(cw-'0'); return nm*fh; } struct Q{ int ls,rs,id; Q(){ls=rs=id=0;} Q(int _ls,int _rs,int _id){ls=_ls,rs=_rs,id=_id;if(ls>rs) swap(ls,rs);} }p[M<<4]; LL now,vm[M<<4]; int n,T,m,fs[M],to[M],nt[M],val[M],od[M],dfn[M],tk[M],sz[M],Root,tot; int cnt,tmp,t[2][M],tp[2],bc,B[M<<1],dep[M],mxs[M],dtp[M],fa[M],bk[M]; void link(int x,int y){nt[tmp]=fs[x],fs[x]=tmp,to[tmp++]=y;} void dfs1(int x,int last){ sz[x]=1,dep[x]=dep[last]+1,fa[x]=last; for(int i=fs[x];i!=-1;i=nt[i]){ if(to[i]==last) continue; dfs1(to[i],x); if(sz[to[i]]>sz[mxs[x]]) mxs[x]=to[i]; sz[x]+=sz[to[i]]; } } void dfs2(int x,int TP){ dtp[x]=TP,dfn[x]=++cnt,bk[cnt]=x; if(mxs[x]) dfs2(mxs[x],TP); else return; for(int i=fs[x];i!=-1;i=nt[i]) if(dep[to[i]]>dep[x]&&to[i]!=mxs[x]) dfs2(to[i],to[i]); } void getans(int &l,int &r,int x){ if(dfn[Root]<dfn[x]||dfn[Root]>=dfn[x]+sz[x]){l=dfn[x],r=dfn[x]+sz[x]-1;return;} if(x==Root){l=1,r=n;return;} int k=Root; while(true){if(dtp[k]!=dtp[x]) k=dtp[k];else break;if(fa[k]!=x) k=fa[k];else break;} if(fa[k]!=x) k=bk[dfn[k]-dep[k]+dep[x]+1];l=dfn[k]+sz[k],r=dfn[k]+n-1; } bool cmp(int x,int y){return val[x]<val[y];} bool cmpQ(Q i,Q j){return B[i.ls]<B[j.ls]||(B[i.ls]==B[j.ls]&&((B[i.ls]&1)?i.rs<j.rs:i.rs>j.rs));} bool cmpid(Q i,Q j){return i.id<j.id;} void con(int kd,int rm){ while(tp[kd]<rm) ++tp[kd],++t[kd][tk[tp[kd]]],now+=t[kd^1][tk[tp[kd]]]; while(tp[kd]>rm) --t[kd][tk[tp[kd]]],now-=t[kd^1][tk[tp[kd]]],--tp[kd]; } int main(){ n=read(),T=read(),memset(fs,-1,sizeof(fs)); for(int i=1;i<=n;i++) od[i]=i,val[i]=read(); for(int i=1;i<n;i++){int x=read(),y=read();link(x,y),link(y,x);} Root=1,dfs1(1,0),dfs2(1,1),cnt=1,sort(od+1,od+n+1,cmp); for(int i=1;i<=n;i++){if(i>1&&val[od[i]]>val[od[i-1]]) ++cnt;tk[dfn[od[i]]]=tk[n+dfn[od[i]]]=cnt;} while(T--){ int tpe=read(),u,v,L1,R1,L2,R2; if(tpe==1){Root=read();continue;} int x=++m; x<<=2,u=read(),v=read(),getans(L1,R1,u),getans(L2,R2,v); p[x]=Q(R1,R2,x),p[x-1]=Q(L1-1,R2,x-1),p[x-2]=Q(R1,L2-1,x-2),p[x-3]=Q(L1-1,L2-1,x-3); } bc=max(70,(int)(1.25*sqrt(n))),tot=(m<<2); for(int i=1;i<=(n<<1);i++) B[i]=(i-1)/bc+1; sort(p+1,p+tot+1,cmpQ); for(int i=1;i<=tot;i++) if(p[i].ls) con(0,p[i].ls),con(1,p[i].rs),vm[p[i].id]=now; for(int i=4;i<=tot;i+=4,Putchar('\n')) now=vm[i]-vm[i-1]-vm[i-2]+vm[i-3],write(now); flush(); return 0; }