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

74 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第5天,点击查看活动详情

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

题目描述

给定一棵具有 nn 个节点的树,每个节点有一个初始权值 aia_i

一共需要进行 mm 次操作,每次操作包括:

  • 1 e 编号为 ee 的边突然消失,使得它所在的那棵树变成了两棵树。

  • 2 u val 编号为 uu 的节点的权值变成了 valval

  • 3 u 进行了一次查询,查询 uu 所在的那棵树的权值之和。

现在你需要来模拟上述事件,以了解树的变迁。

输入格式

第一行为 n,mn, m,如上所述。

第二行有 nn 个数,为 nn 个结点的初始权值,在 10310^3 以内。

下面 n1n-1 行,每行一组 u,vu, v,表示一条边。(保证初始为一棵树)

下面 mm 行有 mm 个操作:

先读入一个 opt\text{opt},表示操作类型。

  • opt=1\text{opt}=1 时,读入 ee,表示删掉读入的第 ee 条边。(保证第 ee 条边存在)

  • opt=2\text{opt}=2 时,读入 u,valu,val,表示把结点 uu 的权值改成 valvalval1000val \le 1000)。

  • opt=3\text{opt}=3 时,读入 uu,表示查询 uu 所在的那棵树的结点权值和。

输出格式

对于每个查询操作,输出一行一个数表示答案。

样例 #1

样例输入 #1

2 3
1 1
1 2
2 2 4
1 1
3 2

样例输出 #1

4

提示

所有测试数据数据满足 1n,m1051 \leq n,m \leq {10}^51ai,val10001 \leq a_i,val \leq 1000

思路分析:

  • 1.有删边但是没有恢复操作,我们可以存下所有的操作,然后进行逆序套路
  • 2.先进行存储操作记录,记录一下没有被删除的边
  • 3.用一个循环,将未被删除的边先连接起来
  • 4.逆序操作
    • 1.如果是1(删边)的话,根据逆序的操作,如果有查询在之前就已经查询过了,我们把这个边加进去。
    • 2.如果是2(修改权值)的话,因为w[i]记录的是最后一次更改完的值,则我们取出栈中的值,当前值等于栈顶的值,sum加上当前值-w的值
    • 3.如果是3(查询)的话,我们只要记录一下当前点的值就行了,但由于现在是逆序状态,所以不能现在输出,要再开一个循环逆序输出!

代码展示

#include<iostream>
#include<vector>
using namespace std;

const int N = 1e5 + 10;
vector<int>v[N];//点,记录点的逆序权值
int w[N];//权重
int pre[N];//前驱结点
int sum[N];//树和
int st[N];//记录边是否会被删除
int res[N];//记录结果

//边
struct edge {
	int a;
	int b;
}e[N];

//操作记录
struct option {
	int u;
	int op;//操作的种类
}opt[N];

//查找函数
int find(int x) {
	return x == pre[x] ? x : pre[x] = find(pre[x]);
}


//合并函数
void join(int x, int y) {
	int fx = find(x);
	int fy = find(y);
	if (fx != fy) {
		pre[fx] = fy;
		sum[fy] += sum[fx];
		sum[fx] = 0;
	}
}

int n, m;
int main()
{
	cin >> n >> m;
	//存入权值
	for (int i = 1; i <= n; i++)cin >> w[i];
	//存入边
	for (int i = 1; i < n; i++)cin >> e[i].a >> e[i].b;
	//存入操作
	for (int i = 1; i <= m; i++) {
		cin >> opt[i].op;
		if (opt[i].op == 1) {
			cin >> opt[i].u;
			st[opt[i].u] = 1;//记录
		}
		else if (opt[i].op == 2) {
			cin >> opt[i].u;
			int u = opt[i].u;
			int value;
			cin >> value;
			v[u].push_back(w[u]);
			w[u] = value;
		}
		else {
			cin >> opt[i].u;
		}
	}

	//逆序初始化
	for (int i = 1; i <= n; i++) {
		sum[i] = w[i];
		pre[i] = i;
	}

	//对于没删除的边进行合并
	for (int i = 1; i < n; i++) {
		if (!st[i])
			join(e[i].a, e[i].b);
	}
	int cnt = 0;
	//逆序处理
	for (int i = m; i > 0; i--) {
		if (opt[i].op == 1) {
			join(e[opt[i].u].a, e[opt[i].u].b);
		}
		else if (opt[i].op == 2) {
			int u = opt[i].u;
			int nowValue = v[u][v[u].size() - 1];
			v[u].pop_back();
			sum[find(u)] += nowValue - w[u];
			w[u] = nowValue;
		}
		else if (opt[i].op == 3) {
			res[++cnt] = sum[find(opt[i].u)];
		}
	}
	for (int i = cnt; i > 0; i--) {
		cout << res[i] << endl;
	}
	return 0;
}