P8710
这个题就很像个并查集,初始状态相当于初始化里的\(fa[i]=i\),建边就是两个连通块合并,发送消息就是连通块整体加一个数。
所以我们考虑带权并查集做法,对于一个连通块的祖先节点\(fx\),维护它当前的权值\(sum[fx]\);至于其他节点\(x\),维护它与祖先节点的差值\(cha[x]\)(\(cha[x]>0\))。
对于\(op=2\)的连通块整体加,只需要将\(x\)所在连通块的祖先节点\(fx\)权值\(+t\)(因为显然整体加一个数后,相对差值并不会变)。
而当\(op=1\)时,除了常规的合并\(x、y\)的祖先节点\(fx、fy\)以外,我们在\(fx、fy\)之间建一条权值为\(sum[fy]-sum[fx]\)的边(假设\(sum[fy]>sum[fx]\)),并令\(cha[fx]=sum[fy]-sum[fx]\),这样对于这个连通块下的任意点\(x\),\(cha[x]\)就等于\(x\)到\(fx\)的边权之和。
至于如何动态的在find函数内统计\(cha[x]\),我们可以用前缀和的思想,\(cha[x]=w(x,fa[x])+cha[fa[x]]\),而\(x\)与\(fa[x]\)合并的时候,\(cha[x]\)就已经统计了\(w(x,fa[x])\),所以只需要在路径压缩的时候令\(cha[x]\)加上\(cha[fa[x]]\)即可。(并不会重复统计\(cha[fa[x]]\)的贡献,因为这里我用的是路径压缩,统计完后就会压缩到祖先节点上,而祖先节点的cha是0,所以这种做法每次只会统计新建边增长的贡献。)
代码:
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
inline int read(){
int x=0;char c=getchar();
while(c<48) c=getchar();
while(c>47) x=(x<<1)+(x<<3)+(c^48),c=getchar();
return x;
}
const int N=1e4+4;
const int M=1e5+5;
int n,m,fa[N],cha[N],sum[N];
inline int FIND(int x){//并查集(路径压缩)
if(fa[x]==x) return x;
int fx=FIND(fa[x]);
cha[x]+=cha[fa[x]];
return fa[x]=fx;
}
inline void merge(int x,int y){
int fx=FIND(x),fy=FIND(y);
if(fx==fy){
return ;
}
if(sum[fx]<sum[fy]){//这里是保证cha[fx]非负
swap(fx,fy);
}
cha[fy]=sum[fx]-sum[fy];//这里相当于在fx、fy间建一条权值为sum[fx]-sum[fy]的边
//或者说理解成合并后fy点权与当前根节点fx差sum[fx]-sum[fy]
fa[fy]=fx;
}
signed main(){
n=read(),m=read();
for(int i=1;i<=n;i++) fa[i]=i;
for(int i=1;i<=m;i++){
int op=read(),x=read(),y=read();
if(op==1){//连通块合并
merge(x,y);
}
if(op==2){//连通块整体加t
int fx=FIND(x);sum[fx]+=y;
}
}
for(int i=1;i<=n;i++){
int fi=FIND(i);
sum[i]=sum[fi]-cha[i];//输出时统计i当前权值
printf("%lld ",sum[i]);
}
return 0;
}

浙公网安备 33010602011771号