洛谷 P4047 [WC2013] 糖果公园 题解

本文详细解析了洛谷P4047题目的解题思路,介绍了如何利用树上带修莫队算法解决给定树上两点间路径的问题。首先通过DFS获取欧拉序和LCA信息,然后在莫队框架下处理时间维度的更新,结合欧拉序性质统计贡献,最终给出完整代码实现。

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

题目传送门

本人写的详细莫队入门教学

题意分析

给定一个树,每次给出两个点 x , y x,y x,y,求这两个点之间的路径上的相关信息,或者修改某个点的值。

算法描述

我们发现,题中所给的操作,可以用带修莫队实现,而把带修莫队放到了树上,这道题就变成了树上带修莫队。

不会树上莫队和带修莫队,可以去看看本人上文中提到的博客无耻的推荐一波自己的博客 ,里面讲的应该比较详细,适合莫队入门。

这道题,我们先用一遍 d f s dfs dfs 将欧拉序求出来,同时预处理出 f f f 数组和 d e p dep dep 数组用于下面的倍增求 L C A LCA LCA。当然求 L C A LCA LCA 的时候也可以用树剖来求,不过求 L C A LCA LCA 不是此题的重点,能求出来即可。对于树上的欧拉序,其实很简单,普通的 d f s dfs dfs 序是每个点第一次被 d f s dfs dfs 到的时候记录下来,那么欧拉序就是每个点进入 d f s dfs dfs 和出 d f s dfs dfs 的时候都记录一下即可。关于欧拉序的性质,本人博客里面也有介绍。简单来说,可以把树上的路径问题,给转化为一段区间,或者是一段区间加上两个点的 L C A LCA LCA

把树上的欧拉序预处理完之后,考虑进行带修莫队。带修莫队的具体实现,其实就是在普通莫队的基础上多了时间这一维。先把区间的左右端点定好,如果时间戳大了,就往回调,小了就往后调。知道区间和时间同时相等的时候,我们才能说该询问区间与我们得到的区间重合。此时的答案就是该次询问的答案。

至于如何在莫队移动左右指针的时候统计答案,直接用题目中给的式子算贡献即可。注意,由于欧拉序的性质,在一段区间中出现两次的点,不在我们要求的区间内。所以我用一个 v i s vis vis 数组记录下来每个在这个区间的点出现次数。每次操作将 v i s [ i ] vis[i] vis[i] ^ = 1 = 1 =1,表示出没出现过即可。如果没有出现过,那么就要加上这个点的贡献,否则要减去这个点的贡献。最后还是欧拉序的性质,看是否要加上 L C A LCA LCA 这个点对答案的贡献即可。

注意,这道题中给出的 c [ i ] c[i] c[i] 并不是对应树上欧拉序的点的 c [ i ] c[i] c[i],所以我们算 c c c 的时候,里面应该传的是欧拉序的编号。

总代码

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <vector>
#include <queue>
#define re register
#define int long long
#define drep(a,b,c) for(re int a(b) ; a>=(c) ; --a)
#define rep(a,b,c) 	for(re int a(b) ; a<=(c) ; ++a)
using namespace std;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch == '-') f=-1 ; ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return x*f;
}
inline void print(int x){
	if(x < 0) putchar('-'),x = -x;
	if(x >= 10) print(x / 10);
	putchar(x % 10 + '0');
}
const int M = 2e5+10;
int head[M],f[M][25],dep[M],v[M],w[M],belong[M],vis[M];
int t[M],cnt[M],c[M],oul[M],st[M],ed[M],typ[M],ans[M];
int n,m,Q,ecnt,oulcnt,qcnt,ccnt,res,B,l=1,r,tim;
struct edge{
	int to,nxt;
}e[M];
inline void addedge(int u,int v){
	e[++ecnt].to = v;
	e[ecnt].nxt = head[u];
	head[u] = ecnt;
}
inline void dfs(int u,int fa){
	dep[u] = dep[fa] + 1;
	f[u][0] = fa;
	rep(i,1,20) f[u][i] = f[f[u][i-1]][i-1];
	st[u] = ++oulcnt;
	oul[oulcnt] = u;
	typ[oulcnt] = c[u];
	for(re int i(head[u]) ; i ; i=e[i].nxt){
		int v = e[i].to;
		if(v == fa) continue;
		dfs(v,u);
	}
	ed[u] = ++oulcnt;
	oul[oulcnt] = u;
	typ[oulcnt] = c[u];
}
inline int getlca(int x,int y){
	if(dep[x] < dep[y]) swap(x,y);
	drep(i,20,0) if(dep[f[x][i]] >= dep[y]) x = f[x][i];
	if(x == y) return x;
	drep(i,20,0) if(f[x][i] != f[y][i]) x = f[x][i],y = f[y][i];
	return f[x][0];
}
struct node{
	int l,r,lca,id,t,bell,belr;
	friend bool operator < (node a,node b){
		if(a.bell == b.bell){
			if(a.belr == b.belr) return a.t < b.t;
			if(a.bell&1) return a.r < b.r;
			return a.r > b.r;
		}
		return a.l < b.l;
	}
}q[M];
struct qwq{
	int pos,val;
}ch[M];
inline void change(int x){
	if(vis[ch[x].pos]){
		t[c[ch[x].pos]]--;
		res -= w[t[c[ch[x].pos]]+1]*v[c[ch[x].pos]];
		t[ch[x].val]++;
		res += w[t[ch[x].val]]*v[ch[x].val];
	}
	swap(c[ch[x].pos],ch[x].val);
}
signed main(){
	n = read(),m = read(),Q = read();
	B = 1145;
	rep(i,1,m) v[i] = read();
	rep(i,1,n) w[i] = read();
	rep(i,1,n-1){
		int u = read(),v = read();
		addedge(u,v),addedge(v,u);
	}
	rep(i,1,n) c[i] = read();
	dfs(1,0);
	rep(i,1,Q){
		int op = read(),x = read(),y = read();
		if(op == 1){
			if(st[x] > st[y]) swap(x,y);
			q[++qcnt].lca = getlca(x,y);
			q[qcnt].id = qcnt;
			q[qcnt].t = ccnt;
			if(q[qcnt].lca == x){
				q[qcnt].l = st[x];
				q[qcnt].r = st[y];
				q[qcnt].bell = st[x]/B;
				q[qcnt].belr = st[y]/B;
				q[qcnt].lca = 0;
			}
			else{
				q[qcnt].l = ed[x];
				q[qcnt].r = st[y];
				q[qcnt].bell = ed[x]/B;
				q[qcnt].belr = st[y]/B;
			}
		}
		else{
			ch[++ccnt].pos = x;
			ch[ccnt].val = y;
		}
	}
	sort(q+1,q+qcnt+1);
	rep(i,1,qcnt){
		while(l > q[i].l){
			l--;
			if(!vis[oul[l]]) t[c[oul[l]]]++,res += w[t[c[oul[l]]]]*v[c[oul[l]]];
			else t[c[oul[l]]]--,res -= w[t[c[oul[l]]]+1]*v[c[oul[l]]];
			vis[oul[l]] ^= 1;
		}
		while(r < q[i].r){
			r++;
			if(!vis[oul[r]]) t[c[oul[r]]]++,res += w[t[c[oul[r]]]]*v[c[oul[r]]];
			else t[c[oul[r]]]--,res -= w[t[c[oul[r]]]+1]*v[c[oul[r]]];
			vis[oul[r]] ^= 1;
		}
		while(l < q[i].l){
			if(!vis[oul[l]]) t[c[oul[l]]]++,res += w[t[c[oul[l]]]]*v[c[oul[l]]];
			else t[c[oul[l]]]--,res -= w[t[c[oul[l]]]+1]*v[c[oul[l]]];
			vis[oul[l]] ^= 1;
			l++;
		}
		while(r > q[i].r){
			if(!vis[oul[r]]) t[c[oul[r]]]++,res += w[t[c[oul[r]]]]*v[c[oul[r]]];
			else t[c[oul[r]]]--,res -= w[t[c[oul[r]]]+1]*v[c[oul[r]]];
			vis[oul[r]] ^= 1;
			r--;
		}
		while(tim < q[i].t) change(++tim);
		while(tim > q[i].t) change(tim--);
		if(q[i].lca){
			t[c[q[i].lca]]++;
			res += w[t[c[q[i].lca]]]*v[c[q[i].lca]];
		}
		ans[q[i].id] = res;
		if(q[i].lca){
			t[c[q[i].lca]]--;
			res -= w[t[c[q[i].lca]]+1]*v[c[q[i].lca]];
		}
	}
	rep(i,1,qcnt) printf("%lld\n",ans[i]);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值