把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【洛谷4689】[Ynoi2016] 这是我自己的发明(莫队)

点此看题面

  • 给定一棵\(n\)个点的有根树,每个点上有一个数。
  • 两种操作,给这棵树换一个根,或是询问有多少种方式从给定两点的子树选出两个相同的数。
  • \(n\le10^5,q\le5\times10^5\)

简单莫队

首先不难想到要把子树对应到\(dfs\)序列上的一个区间。

而对于序列上的这种问题实际上是有原题的:【洛谷5268】[SNOI2017] 一个简单的询问

转化\(dfs\)

经典套路。

如果根不在当前点\(x\)子树内,那么对应的区间就是在原树上对应的\(dfs\)序列区间。

否则,如果根在当前点\(x\)子树内,我们从根向上倍增找到它是在\(x\)的哪个子节点(设其为\(y\))的子树中,那么对应的区间就应该是整个序列抠出\(y\)对应的\(dfs\)序列区间后剩余的一段前缀和一段后缀。

考虑我们将序列复制一遍,那么这种情况也可以表示成一个完整区间了。

然后就结束了。

代码:\(O(q\sqrt n)\)

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100000
#define M 500000 
#define LN 20
#define LL long long
#define add(x,y) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y)
using namespace std;
int n,m,Qt,a[N+5],dv[N+5],sz,bl[2*N+5],ee,lnk[N+5];struct edge {int to,nxt;}e[N<<1];
struct Q
{
	int op,p,a,b;I Q(CI t=0,CI i=0,CI x=0,CI y=0):op(t),p(i),a(x),b(y){}
	I bool operator < (Con Q& o) Con {return bl[a]^bl[o.a]?a<o.a:(bl[a]&1?b<o.b:b>o.b);}
}q[4*M+5];
namespace FastIO
{
	#define FS 100000
	#define tc() (FA==FB&&(FB=(FA=FI)+fread(FI,1,FS,stdin),FA==FB)?EOF:*FA++)
	#define pc(c) (FC==FE&&(clear(),0),*FC++=c)
	int OT;char oc,FI[FS],FO[FS],OS[FS],*FA=FI,*FB=FI,*FC=FO,*FE=FO+FS;
	I void clear() {fwrite(FO,1,FC-FO,stdout),FC=FO;}
	Tp I void read(Ty& x) {x=0;W(!isdigit(oc=tc()));W(x=(x<<3)+(x<<1)+(oc&15),isdigit(oc=tc()));}
	Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
	Tp I void writeln(Ty x) {W(OS[++OT]=x%10+48,x/=10);W(OT) pc(OS[OT--]);pc('\n');}
}using namespace FastIO;
int d,b[2*N+5],dep[N+5],dI[N+5],dO[N+5],f[N+5][LN+5];I void dfs(CI x,CI lst=0)//dfs预处理
{
	RI i;for(i=1;i<=LN;++i) f[x][i]=f[f[x][i-1]][i-1];//预处理倍增数组
	for(dI[x]=++d,b[d]=b[n+d]=a[x],i=lnk[x];i;i=e[i].nxt)
		e[i].to^lst&&(dep[e[i].to]=dep[f[e[i].to][0]=x]+1,dfs(e[i].to,x),0);dO[x]=d;
}
I int Jump(RI x,RI d) {for(RI i=0;i<=LN;++i) (d>>i)&1&&(x=f[x][i]);return x;}//倍增上跳找所在子节点
int c1[N+5],c2[N+5];LL ans[M+5];I void Mo()//莫队
{
	RI i,A=0,B=0;LL t=0;for(sz=sqrt(2*n),i=1;i<=2*n;++i) bl[i]=(i-1)/sz+1;
	for(sort(q+1,q+Qt+1),i=1;i<=Qt;ans[q[i].p]+=q[i].op*t,++i)
	{
		W(A<q[i].a) ++c1[b[++A]],t+=c2[b[A]];W(A>q[i].a) --c1[b[A]],t-=c2[b[A--]];
		W(B<q[i].b) ++c2[b[++B]],t+=c1[b[B]];W(B>q[i].b) --c2[b[B]],t-=c1[b[B--]];
	}for(i=1;i<=m;++i) ~ans[i]&&(writeln(ans[i]),0);
}
int rt;I void Get(CI x,int& l,int& r)//求区间
{
	if(x==rt) return (void)(l=1,r=n);if(dI[rt]<dI[x]||dI[rt]>dO[x]) return (void)(l=dI[x],r=dO[x]);//如果就是根;如果根节点不在子树内
	RI y=Jump(rt,dep[rt]-dep[x]-1);l=dO[y]+1,r=n+dI[y]-1;//找到对应儿子,就是扣去其子树的剩余部分
}
int main()
{
	RI i;for(read(n,m),i=1;i<=n;++i) read(a[i]),dv[i]=a[i];
	for(sort(dv+1,dv+n+1),i=1;i<=n;++i) a[i]=lower_bound(dv+1,dv+n+1,a[i])-dv;//离散化
	RI x,y;for(i=1;i^n;++i) read(x,y),add(x,y),add(y,x);dfs(1);
	RI op,a,b,c,d;for(rt=i=1;i<=m;++i) switch(read(op),op)
	{
		case 1:read(x),rt=x,ans[i]=-1;break;//修改的ans标记为-1
		case 2:read(x,y),Get(x,a,b),Get(y,c,d),q[++Qt]=Q(1,i,b,d),
			q[++Qt]=Q(-1,i,b,c-1),q[++Qt]=Q(-1,i,a-1,d),q[++Qt]=Q(1,i,a-1,c-1);break;//容斥拆成四个区间
	}return Mo(),clear(),0;
}
posted @ 2021-03-26 08:46  TheLostWeak  阅读(32)  评论(0编辑  收藏  举报