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;
}
posted @ 2025-08-11 13:58  qwqSW  阅读(10)  评论(0)    收藏  举报