2025南昌CCPC 挑战图同构 D题解

D. 挑战图同构 题解

题意

给定两张点编号相同的带权无向图 G1, G2

定义一条路径的权值为路径上所有边权的最大值。两点 u, v 的距离 dis(u, v) 是所有 uv 路径权值的最小值;若不连通,则距离为 +inf

要求判断对于所有不同点对 (u, v),两张图中的 dis(u, v) 是否完全相等。

注意这里不允许重新编号,所以并不是传统图同构,而是要求相同编号点之间的距离矩阵完全一致。

关键观察

对于一张带权无向图,若只保留边权 <= x 的边,那么:

  • u, v 在这个子图中连通,说明存在一条路径,使得路径最大边权 <= x
  • 若不连通,说明不存在路径最大边权 <= x

因此,dis(u, v) 等于在按边权从小到大加入边的过程中,u, v 第一次变为连通时的边权。

于是,两张图的所有 dis(u, v) 完全相等,当且仅当:

对于每一个可能的权值 x,加入所有边权 <= x 的边后,两张图得到的连通块划分完全相同。

也就是说,我们不需要真的计算所有点对距离,只需要模拟 Kruskal 加边过程,并在每个权值层结束后比较两张图当前的连通块划分。

做法

分别维护两张图的并查集。

把两张图的边都按权值从小到大处理。每次取当前两张图中最小的边权 w,然后:

  1. G1 中加入所有权值为 w 的边;
  2. G2 中加入所有权值为 w 的边;
  3. 比较这一次发生变化的点所在连通块,在两张图中是否完全相同。

如果某个权值处理完后,两张图的连通块划分不同,则一定存在某些点对在一张图中已经连通,而另一张图中还没有连通,答案为 NO

若所有权值处理完后都一致,则答案为 YES

如何比较连通块

朴素地比较两个连通块中包含的点集会很慢。

你的代码中使用了随机哈希来表示集合:

ull hsh[N], sum1[N], sum2[N];

给每个点 i 随机一个 unsigned long longhsh[i]

一个连通块的哈希值定义为这个连通块内所有点的随机值之和:

sum[root] = hsh[v1] + hsh[v2] + ... + hsh[vk]

并查集合并两个连通块时,也同步合并哈希值:

sum[l] += sum[r];

这样,若两个连通块包含的点集相同,它们的哈希和一定相同;若点集不同,它们的哈希和极大概率不同。

所以比较两个点 i 在两张图中所在连通块是否相同,只需要比较:

sum1[find(i, f1, sum1)] == sum2[find(i, f2, sum2)]

这是一种 Monte Carlo 做法,存在极小概率哈希冲突,但使用 64 位随机哈希后,实际比赛中通常可以接受。

如果想写完全确定性做法,可以用 set / map 维护连通块成员,或者给连通块做确定性编号,但实现会更复杂。

为什么只检查发生变化的点

代码中用全局集合 s 记录本轮合并涉及到的端点:

set<int> s;

每次成功合并时:

s.insert(x);
s.insert(y);

如果某个权值层结束后,两张图的连通块划分发生了变化,那么变化一定出现在本轮被合并影响到的连通块里。

没有参与本轮合并的点,它们所在的连通块没有变化;如果上一轮已经相同,这一轮仍然相同。

因此只检查 s 中的点即可。

代码实现

#include<bits/stdc++.h>
using namespace std;
using ll = long long;
using ull = unsigned long long;
const int N=1e5+5;
// 随机哈希(空间O(n))
ull hsh[N], sum1[N], sum2[N];
mt19937_64 rnd(chrono::steady_clock::now().time_since_epoch().count());
int find(int x,vector<int>&f, ull sum[]){
	if(f[x]<0) return x;
	return f[x]=find(f[x],f,sum);
}
set<int>s;
struct node{
	int w, u, v;
	bool operator<(const node& other) const {
		return w > other.w;
	}
};
void Union(int x,int y,vector<int>&f, ull sum[]){
	int l=find(x,f,sum);
	int r=find(y,f,sum);
	if(l==r) return;
	// 按大小合并,累加哈希值
	if(f[l]<f[r]) swap(l,r);
	f[l]+=f[r];
	f[r]=l;
	sum[l]+=sum[r];
	s.insert(x);
	s.insert(y);
}
int main(){
	// 预处理随机哈希值
	for(int i=1;i<N;i++) hsh[i]=rnd();
	int t;
	cin>>t;
	while(t--){
		int n,m1,m2;
		cin>>n>>m1>>m2;
		// 初始化哈希和
		for(int i=1;i<=n;i++){
			sum1[i]=sum2[i]=hsh[i];
		}
		s.clear();
		priority_queue<node>q1,q2;
		vector<int>f1(n+1,-1),f2(n+1,-1);
		int u,v,w;
		for(int i=1;i<=m1;i++){
			cin>>u>>v>>w;
			q1.push({w,u,v});
		}
		for(int i=1;i<=m2;i++){
			cin>>u>>v>>w;
			q2.push({w,u,v});
		}
		int ok=1;
		while(!q1.empty()&&!q2.empty()){
			int ans;
			s.clear();
			ans=min(q1.top().w,q2.top().w);
			while(!q1.empty()&&q1.top().w==ans){
				auto it=q1.top();q1.pop();
				Union(it.u,it.v,f1,sum1);
			}
			while(!q2.empty()&&q2.top().w==ans){
				auto it=q2.top();q2.pop();
				Union(it.u,it.v,f2,sum2);
			}
			for(int i:s){
				int r1=find(i,f1,sum1);
				int r2=find(i,f2,sum2);
				// 比较哈希和
				if(sum1[r1]!=sum2[r2]){
					ok=0;break;
				}
			}
			if(!ok) break;
		}
		if(ok){
			s.clear();
			while(!q1.empty()){
				auto it=q1.top();q1.pop();
				Union(it.u,it.v,f1,sum1);
			}
			while(!q2.empty()){
				auto it=q2.top();q2.pop();
				Union(it.u,it.v,f2,sum2);
			}
			for(int i:s){
				int r1=find(i,f1,sum1);
				int r2=find(i,f2,sum2);
				if(sum1[r1]!=sum2[r2]) ok=0;
			}
		}
		cout << (ok ? "YES" : "NO") << '\n';
	}
}

正确性证明

引理 1

在一张图中,点对 u, v 的距离 dis(u, v) <= x,当且仅当在只保留边权 <= x 的子图中,u, v 连通。

证明:

dis(u, v) <= x,则存在一条从 uv 的路径,其最大边权不超过 x,所以这条路径上的所有边都在边权 <= x 的子图中,u, v 连通。

反过来,若 u, v 在边权 <= x 的子图中连通,则存在一条路径,其所有边权都不超过 x,路径最大边权不超过 x,因此 dis(u, v) <= x

引理成立。

引理 2

两张图所有点对距离完全相等,当且仅当对任意权值 x,两张图中边权 <= x 的子图具有完全相同的连通块划分。

证明:

由引理 1 可知,某个点对在阈值 x 下是否连通,完全由 dis(u, v) <= x 决定。

如果两张图所有点对距离相等,那么对任意 x,每个点对在两图中的连通性都相同,所以连通块划分相同。

如果对任意 x 连通块划分都相同,那么任意点对第一次变为连通的权值也相同,因此距离相同。

引理成立。

引理 3

算法在每个出现过的边权处理完后检查连通块划分,足以判断所有阈值 x 的连通块划分是否相同。

证明:

连通块划分只会在加入某条边时发生变化,也就是只可能在某个出现过的边权处发生变化。

x 位于两个相邻边权之间,两张图中边权 <= x 的子图与处理完前一个边权后的状态完全相同。

因此只检查出现过的边权即可。

引理成立。

定理

算法输出 YES 当且仅当两张图在题目定义下同构。

证明:

算法按边权从小到大同步处理两张图,并在每个权值层结束后比较两张图的连通块划分。

若算法发现某一层后划分不同,根据引理 2,存在点对距离不同,答案应为 NO

若算法始终没有发现不同,则所有出现过的边权层后的划分都相同。根据引理 3,可知任意阈值下划分都相同。再由引理 2,两张图所有点对距离完全相等,答案应为 YES

定理成立。

复杂度分析

设两张图边数分别为 m1, m2

使用优先队列按边权取边:

  • 建堆 / 入队复杂度为 O((m1 + m2) log(m1 + m2))
  • 并查集操作近似 O(alpha(n))
  • 每次合并只把端点加入检查集合,整体检查次数与成功合并相关。

总时间复杂度约为:

O((m1 + m2) log(m1 + m2))

空间复杂度:

O(n + m1 + m2)

其中随机哈希数组和并查集都是 O(n),优先队列存边为 O(m1 + m2)

代码思路对应

你的代码中:

  • f1, f2:两张图的并查集;
  • sum1, sum2:两张图每个连通块的随机哈希和;
  • q1, q2:分别存储两张图的边,按边权从小到大弹出;
  • Union:合并连通块,同时维护哈希和;
  • s:记录本轮受影响的点,只检查这些点所在连通块是否一致;
  • ok:标记当前是否仍可能同构。

整体逻辑就是:按权值同步做 Kruskal,并判断两张图在每个权值层后的连通块划分是否相同。

posted @ 2026-06-28 18:07  qif711  阅读(6)  评论(0)    收藏  举报