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;
}

 

posted @ 2018-10-16 18:21  OYJason  阅读(156)  评论(0编辑  收藏  举报