树上莫队(括号序列树上莫队)

本文详细介绍了如何在树上应用莫队算法,通过将树转化为括号序列,并利用in、out数组处理节点遍历。内容包括树的DFS遍历,括号序列的生成,以及在树上莫队中左、右指针的移动策略。同时,文章讨论了在处理区间查询时如何消除节点贡献的重复问题,以及如何在莫队基础上添加修改操作。文章以实例解析了树剖、倍增和树剖求LCA等技术在莫队算法中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

普通的莫队只能处理链上问题,那么如果我们想在一棵树上跑莫队的话,我们就要将这颗树压成链状。这里我们引入一种处理树的方法——就是将其变成括号序列
namo什么是括号序列呢?让我们对树进行dfs,当从这个点开始dfs的时候,我们往序列中加入这个点的编号,当这个点的dfs结束时,我们也往序列中加入这个点的编号,那么任意一个点在括号序列中均出现了两次,就像左括号和右括号那样,所以这样的序列被叫做了括号序列。
考虑这颗树的括号序列
在这里插入图片描述
很明显假设我们从1开始dfs的话,这颗树的括号序列就是 1–2–4–4–2–3–5–5–3–1
让我们把所有点第一次加入括号序列时的编号放入in数组中,第二次出现时放入out数组中,那么很显然我们的in数组=[1, 2, 6, 3, 7],我们的out数组=[10, 5, 9, 4, 8]。
得到了括号序列和in、out数组之后,我们就可以开始考虑这种情况下左,右指针的移动情况。

假设我们要统计节点1到节点4的信息,那么我们可以直接遍历{ i n 1 in_1 in1, i n 4 in_4 in4}={1,2,3}。

假设我们要统计节点1到节点5的信息,那么我们可以直接编列{ i n 1 in_1 in1, i n 5 in_5 in5}={1,2,3,4,5,6,7}。此时我们可以发现 i n 2 in_2 in2 o u t 2 out_2 out2 i n 4 in_4 in4 o u t 4 out_4 out4均出现了两次,也就是说节点2和节点4都被便利了两次,但其实这两个节点的贡献是0,所以我们要注意消除这个影响。

假设我们要遍历节点2到节点3的信息,那么我们可以直接便利{ o u t 2 out_2 out2, i n 3 in_3 in3}={5,6},此时我们发现了,它没有遍历到lca(2,3)也就是节点1的信息,此时我们就要把节点1的信息加上了,

我们可以得出一个结论:

若( lca(x,y) == x || lca(x,y) == y ) 那么我们就遍历{ i n x in_x inx, i n y in_y iny},并且消除遍历多次节点的影响。
若(( lca(x,y) ! = x && lca(x,y) ! = y )) 那么我们就遍历{ o u t x out_x outx, i n y in_y iny},并且要加上lca(x,y)的贡献

这里我们用一道例题来解释如何的消除这些影响糖果公园

我们可以用vis数组来标记每一个点,当这个点被访问奇数次的时候,我们就要加上这个节点的贡献,否则,我们就要减去这个节点的贡献,这里我们可以用vis[i]^=1来达到目的。
当然,在这个过程中,我们也需要求某两个节点的lca,这里用倍增和树剖求lca都是可以接受的。另外这个题还需要修改操作,那么我们只需要在莫队的基础上添加一个维度time,就可以完成修改操作了
参考代码

#include<bits/stdc++.h>
#define endl "\n"
#define x first
#define y second
#define Endl endl
typedef long long ll;
const int INF=0x7fffffff;
const double PI=3.14159265359;
using namespace std;
const int N=4e5+10;
int h[N],ne[N],e[N],idx;
int son[N],sz[N],deep[N],fa[N],top[N],dfn,belong[N],v[N],w[N],last[N],cnt,vis[N],times[N],id[N],c[N];
ll ans[N];
int in[N],out[N];
int n,m,q;
ll sum;
struct point{
	int x,y,lca,id,time;
}s1[N];
struct point2{
	int pos,now,pre;
}s2[N];
bool cmp(point a,point b){
	if(belong[a.x]!=belong[b.x]) return belong[a.x]<belong[b.x];
	else {
		if(belong[a.y]==belong[b.y]) return a.time<b.time;
		else return a.y<b.y;
	}
}
void add(int a,int b){
	ne[idx]=h[a],e[idx]=b,h[a]=idx++;
}
void dfs1(int x,int dep,int father){
	sz[x]=1;
	deep[x]=dep;
	fa[x]=father;
	for(int i=h[x];~i;i=ne[i]){
		int j=e[i];
		if(j!=father){
			dfs1(j,dep+1,x);
			sz[x]+=sz[j];
			if(!son[x]||sz[son[x]]<sz[j]) son[x]=j;//树剖部分 
		}
	}
}
void dfs2(int x,int tp){
	in[x]=++cnt;
	id[cnt]=x;
	top[x]=tp;
	if(son[x]) dfs2(son[x],tp);
	for(int i=h[x];~i;i=ne[i]){
		int j=e[i];
		if(j!=fa[x]&&j!=son[x]){//判断轻链 
			dfs2(j,j);
		}
	}
	out[x]=++cnt;
	id[cnt]=x;
} 
int lca(int x,int y){//树剖求lca 
	while(top[x]!=top[y]){
		if(deep[top[x]]<deep[top[y]]) swap(x,y);
        x=fa[top[x]];
    }
    return deep[x]<deep[y]?x:y;
}

void cal(int pos){
	if(vis[pos]){
		sum-=(ll)v[c[pos]]*(w[times[c[pos]]]);
		times[c[pos]]--;
	}
	else{
		times[c[pos]]++;
		sum+=(ll)v[c[pos]]*(w[times[c[pos]]]);
	}
	vis[pos]^=1;
}
void change(int pos,int x){
	if(vis[pos]){
		cal(pos);
		c[pos]=x;
		cal(pos);
	}
	else c[pos]=x; 
}

int main(){
	memset(h,-1,sizeof h);
	idx=0;
	cin>>n>>m>>q;
	for(int i=1;i<=m;i++){
		cin>>v[i];//美味指数 
	}
	for(int i=1;i<=n;i++){
		cin>>w[i];//新奇指数 
	}
	for(int i=1;i<n;i++){
		int a,b;
		cin>>a>>b;
		add(a,b);
		add(b,a);
	}
	for(int i=1;i<=n;i++){
		cin>>c[i];
		last[i]=c[i];
	}
	dfs1(1,1,1);
	dfs2(1,1);
	int cnt1=0,cnt2=0; 
	for(int i=1;i<=q;i++){
		int opt,x,y;
		cin>>opt>>x>>y;
		if(opt==1){
			int k=lca(x,y);
			if(in[x]>in[y]) swap(x,y);
			if(k==x){
				s1[++cnt1].x=in[x],s1[cnt1].y=in[y],s1[cnt1].lca=0,s1[cnt1].time=cnt2,s1[cnt1].id=cnt1;
			}
			else{
				s1[++cnt1].x=out[x],s1[cnt1].y=in[y],s1[cnt1].lca=k,s1[cnt1].time=cnt2,s1[cnt1].id=cnt1;
			}
		}
		else{
			s2[++cnt2]={x,y,last[x]};
			last[x]=y;
		}
	}
	int block=pow(double(cnt),2.0/3);//带修莫队的最优块长 
	for(int i=1;i<=cnt;i++){
		belong[i]=(i-1)/block+1;
	}	
	sort(s1+1,s1+1+cnt1,cmp);
	int L=1,R=1;
	cal(id[1]);
	for(int i=1;i<=cnt1;i++){
		for(int j=s1[i-1].time+1;j<=s1[i].time;j++){
			change(s2[j].pos,s2[j].now);
		}
		for(int j=s1[i-1].time;j>s1[i].time;j--){
			change(s2[j].pos,s2[j].pre);
		}
		int l=s1[i].x,r=s1[i].y;
		while(l<L) cal(id[--L]);
		while(l>L) cal(id[L++]);
		while(r<R) cal(id[R--]); 
		while(r>R) cal(id[++R]);
		if(s1[i].lca){
			cal(s1[i].lca);
			ans[s1[i].id]=sum;
			cal(s1[i].lca);
		}
		else{
			ans[s1[i].id]=sum;
		}
	}
	for(int i=1;i<=cnt1;i++){
		cout<<ans[i]<<endl;
	}
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值