洛谷T160512 G - 森林

洛谷T160512 G - 森林

这道题是小编在参加第三届“传智杯”全国大学生IT技能大赛(初赛A组)时碰到的(题目链接),这道题可以算是有关树的基础算法题,可以当作树算法的练手题。

题目

题目链接
YYH Land(Yoauld, Youthful & Happy Land)是位于炽蓝仙野的一片神奇的国度,那里的人们过着无拘无束的的快乐生活。清蒸鱼是一个尽职尽责的 YYH Land 护林者。他负责每天维护 YYH Land 的森林。在最开始的时候,YYH Land 只有一棵具有 n 个节点的树,每个节点有一个灵力值 v。
由于 YYH Land 是一片神奇的国度,YYH Land 的树也有一些神奇的能力,具体来说它满足如下操作:
1 e:编号为 e 的边突然消失,使得它所在的那棵树变成了两棵树。
2 u val:编号为 u 的节点的灵力值变成了 val。
3 u:清蒸鱼进行了一次查询,查询 u 所在的那棵树的灵力值之和。
现在你需要帮助清蒸鱼,来模拟上述事件,以了解森林的变迁。

输入格式
第一行为 n, m,如上所述。
第二行有 n 个数,为 n 个结点的初始权值,在 10^3 以内。
下面 n-1行,每行一组 u, v,表示一条边。(保证初始为一棵树)
下面 m 行有 m 个操作:
先读入一个opt,表示操作类型。
opt=1 时,读入 e,表示删掉读入的第 e 条边。(保证第 e 条边存在)
opt=2 时,读入 u,val,表示把结点 u 的权值改成 val(val ≤1000)。
opt=3 时,读入 u,表示查询 u 所在的那棵树的结点权值和。

输入输出样例
输入 #1
2 3
1 1
1 2
2 2 4
1 1
3 2
输出 #1
4

说明/提示
对于 30% 的数据,满足 1 <= n,m <= 10;
对于 50% 的数据,满足 1 <= n,m <= 1000;
对于另外 20% 的数据,满足只有 2,3 操作;
对于 100% 的数据,满足 1 <= n,m <= 10^5 , 1≤v,val≤1000。

题解

题目中明确告知输入的确保是一个树,但是并没有说明输入的是有向树还是无向树,因此,小编只能按照输入的是无向树来处理了。本道题可以将无向图装换成有向图进行优化,同时不会对结果产生影响。
首先,题目中一共有三个操作,可以分别抽象为:分裂树、更改树上节点值、求节点所在树的所有节点值的和。相关操作详见链接
(1)求节点所在树的所有节点值的和(对应opt=3):这个是本道题的突破点,如果遇到这个指令,就对这个节点所在树的所有节点进行求和,查看数据范围后,就可以轻松地看出会超时,因此,需要运用树的特性,进行优化,一个树的所有节点的和可以在根节点处进行保存,每一个节点都存储其所有子树上的节点和,最后汇总到根节点,因此,给一个节点,只要找到其所在树的根节点即可。
(2)更改树上的节点值(对应opt=2):如果对一个节点的数值进行更改,其并不会影响其子树,但是其所有父节点都会受到影响。因此利用循环找到其所有父节点(包括父节点的父节点),进行相应的修改即可,这一过程的时间复杂度是O(logn)的。
(3)分裂树(对应opt=1):删除边的两个节点是具有父子关系的,子节点从原来的树中分裂下来,形成新的树,因此,需要将子节点更新为新形成树的根节点,而新生成的树从原树脱离下来,会对原树造成影响,因此需要对其所有父节点进行更新。
代码如下:

#include<iostream>
#include<cstdio>
#include<vector>
#include<queue>
#include<cstring>
using namespace std;
int n, m;
struct Node
{
	int father;
	int value;
	vector<int> child;
	int sum;
}node_temp[200000], node[200000];
int u[200000];
int v[200000];
queue<int> q;
int vis[200000];
int main()
{
	int i, j, vel;
	cin >> n >> m;
	for (i = 1; i <= n; i++)
	{
		cin >> vel;
		node[i].value = vel;
		node[i].father = -1;
	}
	for (i = 1; i < n; i++)
	{
		cin >> u[i] >> v[i];
		node_temp[u[i]].child.push_back(v[i]);
		node_temp[v[i]].child.push_back(u[i]);
	}
	memset(vis, 0, sizeof(vis));
	q.push(1);
	vis[1] = 1;
	while (!q.empty())
	{
		int top = q.front();
		q.pop();
		for (j = 0; j < node_temp[top].child.size(); j++)
		{
			if (vis[node_temp[top].child[j]] == 0)
			{
				q.push(node_temp[top].child[j]);
				vis[node_temp[top].child[j]] = 1;
				node[top].child.push_back(node_temp[top].child[j]);
				node[node_temp[top].child[j]].father = top;
			}
		}
	}
	for (i = 1; i <= n; i++)
	{
		if (node[i].child.size() == 0)
		{

		}
		int father = node[i].father;
		while (father != -1)
		{
			node[father].sum = node[father].sum + node[i].value;
			father = node[father].father;
		}
	}
	int opt;
	for (i = 1; i <= m; i++)
	{
		cin >> opt;
		if (opt == 1)
		{
			int e;
			cin >> e;
			int flag = 0;
			for (vector<int>::iterator it = node[u[e]].child.begin(); it != node[u[e]].child.end(); it++)
			{
				if (*it == v[e])
				{
					flag = 1;
					node[u[e]].child.erase(it);
					break;
				}
			}
			for (vector<int>::iterator it = node[v[e]].child.begin(); it != node[v[e]].child.end(); it++)
			{
				if (*it == u[e])
				{
					flag = 2;
					node[v[e]].child.erase(it);
					break;
				}
			}
			if (flag == 1)
			{
				int father = u[e];
				while (father != -1)
				{
					node[father].sum = node[father].sum - (node[v[e]].sum + node[v[e]].value);
					father = node[father].father;
				}
				node[v[e]].father = -1;
			}
			else if (flag == 2)
			{
				int father = v[e];
				while (father != -1)
				{
					node[father].sum = node[father].sum - (node[u[e]].sum + node[u[e]].value);
					father = node[father].father;
				}
				node[u[e]].father = -1;
			}
		}
		else if (opt == 2)
		{
			int a, b;
			cin >> a >> b;
			int temp = node[a].value;
			node[a].value = b;
			int father = node[a].father;
			while (father != -1)
			{
				node[father].sum = node[father].sum - temp + b;
				father = node[father].father;
			}
		}
		else if (opt == 3)
		{
			int a;
			cin >> a;
			int father = node[a].father;
			int son = a;
			while (father != -1)
			{
				son = father;
				father = node[father].father;
			}
			cout << node[son].sum + node[son].value << endl;
		}
	}
	return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值