AcWing 2069. 网络分析(并查集,路径压缩时维护树上差分)(y总代码超全注释版)
原题链接
困难
作者:
抽筋怪兽
,
2024-03-21 15:29:17
,
所有人可见
,
阅读 38
//思路:并查集,通过增加根节点的值,表示整棵树所有下面的结点都加上这个值
//对于操作1,不能直接将a树连到b树上,需要将a结点减去b结点的权值,才不会导致错误
//并查集操作时,如果需要把b到根的路径压缩,数学归纳法可以证明,d[b]应更改为d[p[b]]+d[b]
//很考察对并查集的理解!每次调用find(a)函数,会保证a要么是父结点,要么它的父结点是根结点(第三层以下的结点更新后连到第二层)
//什么叫树上差分?每个结点的实际值是它走到根节点的权值之和
//这样操作只保证了第一第二层的结点是被维护更新好了的,如果要输出哪个点的权值,需要先对这个点find一下,保证更新好
#include <iostream>
using namespace std;
const int N = 10005,M = 100005;
int n,m;
int p[N],d[N];//并查集数组p,d存每个结点的权值
int find(int x)//根和根的孩子是保证维护好了的,但是第三层往后的需要更新,用上面写的数学归纳法思路
//也就是如果x在第三层及以下,这里需要把它的权值处理好之后连到第二层!
{
if(p[x]==x||p[p[x]]==p[x])//如果这个结点或者它的父结点就是根节点,则不用修正操作
return p[x];
int r=find(p[x]);//先将它的父结点处理完,并合并
d[x]+=d[p[x]];//父结点的权值是正确的,且父结点在第二层!!(也就是说父结点的权值是它的真实值-根结点权值)
//因此当前结点加上父结点的权值之后,权值就是自己的权值+父结点的权值-根节点权值。
//当前结点权值处理完毕,可以直接连到根结点去,作为第二层。
//当前结点权值+根结点权值就会是真实值
p[x]=r;//前面处理到了根节点,这里可以直接将x结点连上去了
return r;
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
p[i]=i;//并查集初始化操作!勿忘
while(m--)
{
int t,a,b;
scanf("%d%d%d",&t,&a,&b);
if(t==1)
{
a=find(a),b=find(b);
if(a!=b)
{
d[a]-=d[b];//将a根直接连到b根上,维护好a结点的信息,这时候不保证a下面的结点的信息被维护好了!!
p[a]=b;
}
}
else
{
a=find(a);
d[a]+=b;//根结点+b
}
}
for(int i=1;i<=n;i++)
if(i==find(i))//i就是根结点
printf("%d ",d[i]);
//因为前面调用过了find(i),这里可以保证i是第二层的结点,即保证它的值被正确更新过
else
printf("%d ",d[i]+d[p[i]]);//权值=当前结点加到根结点的权值
cout<<endl;
return 0;
}