原题链接:
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;
}