题解:洛谷 P9984([USACO23DEC] A Graph Problem P)

Link

1. Description

给出一张连通的无向图,包含编号为 \(1\dots N\) 的节点和编号为 \(1\dots M\)\(2 \le N \le 2\cdot 10^5\)\(N - 1 \le M \le 4 \cdot 10^5\))的边,下边的操作将被实施:

  1. 假设集合 \(S=\{v\}\),变量 \(h=0\)
  2. \(|S|<N\),重复执行:
    1. 仅有一个顶点在集合 \(S\) 中的边中,找到编号最小的那条,编号记为 \(e\)
    2. \(e\) 不在 \(S\) 中的那个顶点加入集合 \(S\)
    3. \(h\) 修改为 \(10h+e\)
  3. 返回 \(h\)\(10^9+7\) 取模的值。

输出这个过程的全部返回值。

2.Solution

给出一种只需使用启发式合并的并查集,而无需路径压缩的解法。

我们发现这个过程相当于以 \(v\) 为初始节点,生成一棵最小生成树,因此考虑以边的编号为边权,建出 Kruskal 重构树,然后在构造 Kruskal 重构树的过程中求解答案。

考虑一条边 \(e\),它将连接 \(u,v\) 所在的连通块,此时考虑 \(u\) 所属联通块中的点答案如何修改。

显然,对于 \(u\) 所属连通块中的一个点的访问顺序,有以下结论:

  1. 连接后原来 \(u\) 所属连通块中的边仍然会被优先访问,且访问顺序不变。
  2. \(u\) 所属连通块中的边被遍历完之后,必然优先访问 \(e\)
  3. 访问 \(e\) 之后,将访问 \(v\) 所属联通块中的所有边,且访问顺序和 \(v\) 一致。

以上结论皆由边编号的大小关系得出,读者可以自己思考原因。

所以,对于 \(u\) 所属联通块中的一个点 \(x\),它的答案将从 \(ans_x\) 变为 \((ans_x\times 10+e)\times 10^{cnt_v}+ans_v\),其中 \(cnt_v\) 表示 \(v\) 这一联通块中的边数。

拆开括号,则有 \(ans_x\) 变为 \(ans_x\times 10^{cnt_v+1}+e\times 10^{cnt_v}+ans_v\),也就是对于 \(u\) 所属联通块中的答案,先乘上 \(10^{cnt_v+1}\) 后加上 \(e\times 10^{cnt_v}+ans_v\)

对于 \(v\) 所属的联通块中的点同理,先乘上 \(10^{cnt_u+1}\) 后加上 \(e\times 10^{cnt_u}+ans_u\)

这里可以用并查集维护连通块,同时使用永久化标记 \(add,mul\) 维护修改,当我们合并两个连通块 \(u,v\) 时,我们需要维护 \(add_v,mul_v\),保证修改后的 \(add^{\prime}_v,mul^{\prime}_v\) 满足:

\[\forall x,x\times mul_v+add_v=(x\times mul^{\prime}_v+add^{\prime}_v)\times mul_u+add_u \]

解方程,有 \(mul^{\prime}_v=\frac{mul_v}{mul_u},add^{\prime}_v=\frac{add_v-add_u}{mul_u}\)

通过以上维护,虽然是构建 Kruskal 重构树时求解,但是此时无需加入新的节点,也无需保证父子关系,可以直接使用启发式合并并查集维护连通块并进行修改。

最后 \(i\) 的答案就是以 \(i\) 为起点,到根的路径上的点的标记对初始值为 \(0\) 的变量依次进行修改的结果。

3. Code

/*by qwer6*/
/*略去缺省源与快读快写*/
const int N=2e5+5,mod=1e9+7;
int n,m;
int pw[N];
int Add(int x,int y){
	x+=y;
	return x>=mod?x-mod:x;
}
int Mul(int x,int y){
	long long res=1ll*x*y;
	return res>=mod?res%mod:res;
}
int Sub(int x,int y){
	x-=y;
	return x<0?x+mod:x;
}
int binpow(int a,int b){
	int res=1;
	while(b){
		if(b&1)res=Mul(res,a);
		a=Mul(a,a);
		b>>=1;
	}
	return res;
}
int Inv(int x){
	return binpow(x,mod-2);
}
struct DSU{
	int n;
	int fa[N],siz[N],add[N],mul[N];
	void init(int _n=0){
		n=_n;
		for(int i=1;i<=n;i++)fa[i]=i,siz[i]=1,add[i]=0,mul[i]=1;
	}
	void Tag(int x,int v1,int v2){
		add[x]=Add(Mul(add[x],v1),v2);
		mul[x]=Mul(mul[x],v1);
	}
	int find(int x){
		if(x==fa[x])return x;
		return find(fa[x]);
	}
	void merge(int x,int y){
		x=find(x),y=find(y);
		if(x==y)return ;
		if(siz[x]<siz[y])swap(x,y);
		int inv=Inv(mul[x]);
		mul[y]=Mul(mul[y],inv);
		add[y]=Mul(Sub(add[y],add[x]),inv);
		fa[y]=x;
		siz[x]+=siz[y];
		return ;
	}
	int cal(int x,int &ans){
		ans=Add(Mul(ans,mul[x]),add[x]);
		if(x==fa[x])return x;
		return cal(fa[x],ans);
	}
}dsu;
signed main(){
	read(n),read(m);
	pw[0]=1;
	for(int i=1;i<=n;i++)pw[i]=Mul(pw[i-1],10); 
	dsu.init(n);
	for(int i=1,u,v,valu,valv;i<=m;i++){
		read(u),read(v);
		valu=valv=0;
		u=dsu.cal(u,valu),v=dsu.cal(v,valv);
		if(u==v)continue;
		dsu.Tag(u,pw[dsu.siz[v]],Add(Mul(i,pw[dsu.siz[v]-1]),valv));
		dsu.Tag(v,pw[dsu.siz[u]],Add(Mul(i,pw[dsu.siz[u]-1]),valu));		
		dsu.merge(u,v);
	}
	for(int i=1,ans;i<=n;i++){
		ans=0;
		dsu.cal(i,ans);
		write(ans),Nxt;
	}
}
posted @ 2025-05-08 21:18  陈牧九  阅读(27)  评论(0)    收藏  举报