【Luogu】 P4689 [Ynoi2016] 这是我自己的发明

文章讲述了如何处理树结构上的换根操作对子树dfn序的影响,并将问题转化为区间查询。通过dfn序的倍长,可以将子树变为连续序列,然后利用区间查询的方法解决。此外,文章提到了对每个点的子树使用vector记录dfn序,以及二分查找的优化策略。
摘要由CSDN通过智能技术生成

题目链接

点击打开链接

题目解法

对于子树上的操作,首先想到的就是dfn序

通过dfn序可以把一个子树压成一段连续的区间

我们考虑换根操作对一个点的子树的dfn序的影响

 对于上面的树,dfn序为:1,2,4,5,6,7,3

把初始根 1 换成 5 后,2 的子树有 [ 2,4,5,6,7 ] 变成了 [ 1,2,3,4 ]

发现如果替换的根 root 在 x 的子树中时,

那么在根为 root 时 x 的子树就是刨去 root 所在的 x 的子节点的子树 的序列

如果替换的根 root 不在 x 的子树中时,

那么 x 的子树 和 根为 1 时 x 的子树一样

这样会将子树变成 1-2 段序列

我们将 dfn序 倍长一下,就可以将子树变成 1 段连续的序列

其中求 root 所在的 x 的子树可以用对每个点开一个 vector 记录儿子的 dfn 序,然后二分一下即可

也可以用无脑的倍增思想(但我不想)

问题转化成了如何求 [ l1 , r1 ],[ l2 , r2] 中相同的数的个数

这就和SNOI2017 一个简单的询问非常相似了

那题的做法可以参考题解

于是将每个询问拆成 4 个询问就可以了

时间复杂度 O(n logn+4m\sqrt{n})

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair<int,int> pii;
const int N(200100),M(500100),inf(0x3f3f3f3f); 
struct Query{
	int id,l,r,neg;
}query[M<<2];
int n,m,mq,a[N],disc[N],clo;
int B,pos[N],cnt1[N],cnt2[N];
int top,dfn[N],rv[N],siz[N];
vector<int> vec[N];
vector<pii> start[N];
LL res,ans[M];
inline int read(){
	int FF=0,RR=1;
	char ch=getchar();
	for(;!isdigit(ch);ch=getchar())
		if(ch=='-')
			RR=-1;
	for(;isdigit(ch);ch=getchar())
		FF=(FF<<1)+(FF<<3)+ch-48;
	return FF*RR;
}
void dfs(int u,int fa){
	siz[u]=1,dfn[u]=++top,rv[top]=u;
	for(int i=0;i<vec[u].size();i++){
		int v=vec[u][i];
		if(v!=fa){
			dfs(v,u);
			siz[u]+=siz[v];
			start[u].push_back(make_pair(dfn[v],v));
		}
	}
}
void calc(int &l,int &r,int x,int root){
	if(x==root)
		l=1,r=n;
	else if(dfn[x]<=dfn[root]&&dfn[root]<=dfn[x]+siz[x]-1){
		int pos=upper_bound(start[x].begin(),start[x].end(),make_pair(dfn[root],inf))-start[x].begin()-1;
		int v=start[x][pos].second;
		l=dfn[v]+siz[v],r=dfn[v]+n-1;
	}
	else
		l=dfn[x],r=dfn[x]+siz[x]-1;
}
void add_edge(int l1,int r1,int l2,int r2,int x){
	query[++clo]={x,r1,r2,1},query[++clo]={x,l1-1,l2-1,1};
	query[++clo]={x,r1,l2-1,-1},query[++clo]={x,l1-1,r2,-1};
}
bool cmp(const Query &x,const Query &y){
	if(pos[x.l]^pos[y.l])
		return pos[x.l]<pos[y.l];
	return pos[x.l]&1?x.r<y.r:x.r>y.r;
}
int main(){
	n=read(),m=read();
	for(int i=1;i<=n;i++)
		a[i]=disc[i]=read();
	sort(disc+1,disc+n+1);
	int tot=unique(disc+1,disc+n+1)-disc-1;
	for(int i=1;i<=n;i++)
		a[i]=a[i+n]=lower_bound(disc+1,disc+tot+1,a[i])-disc;
	for(int i=1,x,y;i<n;i++){
		x=read(),y=read();
		vec[x].push_back(y);
		vec[y].push_back(x);
	}
	dfs(1,-1);
	for(int i=1;i<=n;i++)
		rv[i+n]=rv[i];
	int root=1;
	for(int i=1,op;i<=m;i++){
		op=read();
		if(op==1)
			root=read();
		else{
			int x=read(),y=read(); 
			int x_l,x_r,y_l,y_r;
			calc(x_l,x_r,x,root),calc(y_l,y_r,y,root);
			add_edge(x_l,x_r,y_l,y_r,++mq);
		}
	}
	B=sqrt(n<<1);
	for(int i=1;i<=n<<1;i++)
		pos[i]=(i-1)/B+1;
	sort(query+1,query+clo+1,cmp);
	for(int k=1,i=0,j=0;k<=clo;k++){
		int l=query[k].l,r=query[k].r,id=query[k].id,neg=query[k].neg;
		if(!l||!r)
			continue;
		while(i<l) i++,res+=cnt2[a[rv[i]]],cnt1[a[rv[i]]]++;
		while(i>l) res-=cnt2[a[rv[i]]],cnt1[a[rv[i]]]--,i--;
		while(j<r) j++,res+=cnt1[a[rv[j]]],cnt2[a[rv[j]]]++;
		while(j>r) res-=cnt1[a[rv[j]]],cnt2[a[rv[j]]]--,j--;
		ans[id]+=neg*res;
	} 
	for(int i=1;i<=mq;i++)
		printf("%lld\n",ans[i]);
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值