第三届传智杯初赛A组G题 - 森林

原题链接:

        G - 森林 - 洛谷https://www.luogu.com.cn/problem/T160512

题目背景

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

说明/提示


对于 100% 的数据,满足 1≤n,m≤10^5,1≤v,val≤1000。

解题思路:

        数据范围达到10e5,需要使用Onlogn算法,所以模拟加dfs找联通块灵力树基本上是行不通的(我没试)但是复杂度到O(nm)是一定会超的

        所以我考虑逆序操作,用并查集维护。

        每删除一条边打上标记,然后逆序到删边的操作时再把边连上,最后用ans数组保存答案,逆序输出。

        这题个人感觉比F题简单不知道为啥A的人少,没什么思维难度

AC代码:

        

#include<bits/stdc++.h>
using namespace std;
struct node{
	int u,v;
}e[100005];//保存边结点
int num[100005];//保存每个结点的灵力值
int sum[100005];//保存以该结点为祖先的灵力值总和
int ans[100005];//保存答案,因为是逆序操作保存答案的,最终要逆序输出
int vis[100005];
vector<int>	val[100005];//保存每个结点的历史值,每修改一次就插入到尾部                      
                       //逆序操作到的时候从尾部取就行了
int f[100005];//保存祖先
struct cz{//保存每一次操作以及操作的结点
	int opt;
	int u;
}cc[100005];
int opt,cnt;
int find(int x)//基础并查集
{
	return x==f[x]?x:f[x] = find(f[x]);
}
void merge(int x,int y)
{
	int xx = find(x),yy = find(y);
	if(xx!=yy)
	{
		f[xx] = yy;
		sum[yy]+=sum[xx];//合并灵力值之和
		sum[xx] = 0;
	}
}
int n,m;
int main()
{
	cin>>n>>m;
	for(int i = 1;i<=n;++i)
		cin>>num[i];
	for(int i = 1;i<n;++i)//输入边
		cin>>e[i].u>>e[i].v;
	for(int i = 1;i<=m;++i)//输入操作
	{
		cin>>cc[i].opt;
		if(cc[i].opt==1)
		{
			cin>>cc[i].u;
			vis[cc[i].u] = 1;
		}
		if(cc[i].opt==2)
		{
			cin>>cc[i].u;
			int v;
			cin>>v;
			val[cc[i].u].push_back(num[cc[i].u]); //将该结点当前灵力值插入到最新历史版本中
			num[cc[i].u] = v;//更新灵力值
		}
		if(cc[i].opt==3)
			cin>>cc[i].u;
	}
	for(int i = 1;i<=n;++i)
		sum[i] = num[i],f[i] = i;
	for(int i = 1;i<n;++i)
	{
		if(!vis[i])
			merge(e[i].u,e[i].v);
	}
	for(int i = m;i>0;--i)//逆序操作
	{
		if(cc[i].opt==1)//碰到断边的就连回去
			merge(e[cc[i].u].u,e[cc[i].u].v);
		if(cc[i].opt==2)
		{
			int v = cc[i].u;
			int now = val[v][val[v].size()-1];
			val[v].pop_back();
			sum[find(v)]+=now-num[v];//更新该树的灵力值总和
			num[v] = now;//返回历史的最新版本
		}
		if(cc[i].opt==3)
		{
			ans[++cnt] = sum[find(cc[i].u)];//不多赘述
		}
	}
	for(int i = cnt;i>0;--i)//逆序输出答案
		cout<<ans[i]<<endl;
	return 0;
}

         

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值