首页 > 其他分享 >T292115 [传智杯 #5 练习赛] 树的变迁(并查集+倒序操作处理树分裂)

T292115 [传智杯 #5 练习赛] 树的变迁(并查集+倒序操作处理树分裂)

时间:2022-11-22 14:02:15浏览次数:71  
标签:传智杯 练习赛 int back 权值 操作 倒序 find op

T292115 [传智杯 #5 练习赛] 树的变迁


题目大意:

给定一棵具有 \(n\) 个节点的树,每个节点有一个初始权值 \(a_i\)。
一共需要进行 \(m\) 次操作,每次操作包括:
1.1 e 编号为 \(e\) 的边突然消失,使得它所在的那棵树变成了两棵树。
2.2 u val 编号为 \(u\) 的节点的权值变成了 \(val\)。
3.3 u 进行了一次查询,查询 \(u\) 所在的那棵树的权值之和。
现在你需要来模拟上述事件,以了解树的变迁。


解题思路:

观察我们需要进行的三种操作,操作2,3其实相对容易进行处理,最难处理的就是对一棵树进行分裂并统计子树的权值之和,所以我们不妨换个思路,倒着想:
考虑当所有操作结束以后,逆向进行操作,因为操作已经结束,所以分裂完的子树内部是相互连接的,所以整个图就变成了多个集合,假如我们要倒着从最后一个操作逆向回到初始状态,那么就等同于将原本分裂的各个子树集合合并起来并更新权值,这对我们来说是容易维护子树权值的(合并时同时合并子树权值)
此时对子树的权值的更新(操作2)就等同于维护a[find(u)] += (last- now),权值变换所带来影响

思路理清楚之后就是考虑怎么维护这些操作了,因为基本只涉及到了集合的合并,所以很容易想到用并查集来维护


代码实现:

#include<bits/stdc++.h>
using namespace std;
# define int long long
# define endl "\n"
# define mk(a,b) make_pair(a,b)
const int N = 2e5 + 10, inf = 0x3f3f3f3f;
typedef pair<int,int> pii; 
struct node{
	int a,b;
} edg[N];
int a[N];//最终的权值 
int vis[N];//正向所有操作完成后第i条边是否联通 
int f[N];
vector<int> cal[N];//权值变动 
vector<pii> ans;//操作的集合 
int find(int x){
	if(x == f[x]) return x;
	return f[x] = find(f[x]);
}

void solve() {
	int n,m;
	cin>>n>>m;
	for(int i = 1;i <= n;++i) {//读入权值 
		f[i] = i;
		cin>>a[i];
		cal[i].push_back(a[i]);
		vis[i] = 0;	
	}
	for(int i = 1;i < n;++i){//读入边 
		int x,y;
		cin>>x>>y;
		edg[i] = {x,y};
	}
	while(m--){
		int op,x;
		cin>>op>>x;
		if(op == 1){
			vis[x] = 1;
		}
		if(op == 2){
			cin>>a[x];//节点x的权值变化为 
			cal[x].push_back(a[x]);// 保存节点x每次变化后的权值,方便之后维护对子树权值之和的影响 
		}	
		ans.push_back(mk(op,x));
	}
	
	for(int i = 1;i < n;++i){
		if(!vis[i]){//将正向操作最终状态合并出来 
			int x = edg[i].a,y = edg[i].b;
			int fx = find(x),fy = find(y);
			f[fx] = fy;
			a[fy] += a[fx];
		}
	}
	
	reverse(ans.begin(),ans.end());//逆向操作 
	vector<int> p;//逆向答案集合 
	for(auto [op,x]:ans){
		if(op == 1){//分裂改为合并 
			int xx = edg[x].a,yy = edg[x].b;
			int fx = find(xx),fy = find(yy);
			f[fx] = fy;
			a[fy] += a[fx];
		} 
		if(op == 2){//修改权值 
			int last = cal[x].back();//last表示节点x的最终状态 
			cal[x].pop_back();
			int now = cal[x].back();//now表示逆向修改后的状态 
			a[find(x)]+=(now-last);//对整个子树的影响就是now-last 
		}
		if(op == 3){
			p.push_back(a[find(x)]);
		}
	}
	reverse(p.begin(),p.end());//倒转回正向输出结果 
	for(auto v:p) cout<<v<<endl;
}
int tt;
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	tt = 1;
//	cin >> tt;
	while (tt--) solve();


	return 0;
}

标签:传智杯,练习赛,int,back,权值,操作,倒序,find,op
From: https://www.cnblogs.com/empty-y/p/16914921.html

相关文章