【图论】CF1508C Complete the MST

Problem Link

有一张 \(n\) 个点的完全图,其中 \(m\) 条边已经标有边权。你需要给剩下的边都标上权值,使得所有边权的异或和为 \(0\),并且整张图的最小生成树边权和最小。

\(n,m\le 10^6, 1\le w_i<2^{30}\)


注意到这道题是要让最小值最小,所以全程的决策都是由我们来决定的。

设已染色的边权和为 \(S\),那可以发现不会染两条以上的边,理由是 \(x+y\ge x\oplus y\)。只用考虑染一条 \(S\)

\(ans'\) 为视每个补图连通块为一个点,最小生成树边权和。考虑什么情况下这条 \(S\) 边压根不会被算入答案。当所有未确定的边不构成森林的时候答案一定是 \(ans'\),因为随便弄出一棵最小生成树都一定有一条边不在里面,染成 \(S\) 即可。

否则最小生成树是森林,此时点数已经是 \(O(\sqrt m)\) 级别,可以直接枚举哪条边是 \(S\),时间复杂度 \(O(m\sqrt m)\)。如果这么做这道题就没意思了。

继续推性质。考虑我们有没有比 \(ans'+S\) 更优的方案。首先拿出我们当前的最小生成树,称其为 \(T\),则如果我们能省下一条补图的边不被选(用来放 \(S\)),那么在这棵补图树中割掉这条边后,一定会有恰好一边没在当前连通块中,所以需要新增恰好一条原图的边;反之,如果新增一条原图的边,且这条边的两端\(T\) 中不连通,则一定存在一条补图的树边可以被割掉。这两者形成一种双射。

所以从小到大枚举每条边看两个端点连不连通即可。

至于如何求出补图连通块,我们维护一个链表为当前还没有选的点,每次拿出链头,表示找到一个新的连通块,然后进行 bfs,每次对于一个点 \(u\),暴力遍历在链表中的所有点,如果不和 \(u\) 有连边则从链表中删除加入队列。由于每个点至多被删一次,并且每条边至多被访问一次而贡献时间复杂度,所以这部分是均摊线性的。

总时间复杂度 \(O(m\log m)\),瓶颈在于排序和用 set 判断边是否存在

点击查看代码
#include <bits/stdc++.h>
#define For(i,a,b) for(int i=a;i<=b;i++)
#define Rev(i,a,b) for(int i=a;i>=b;i--)
#define Fin(file) freopen(file,"r",stdin);
#define Fout(file) freopen(file,"w",stdout);
using namespace std;
const int N=2e5+5; using ll = long long;
struct Edge{int x,y,z;bool operator<(const Edge& ed)const{return z<ed.z;};}ed[N]; int ecnt;
int n,m,cp[N],ccnt,siz[N],ec[N],fa[N],flg[N]; set<int> S[N];
int getfa(int x) { return x==fa[x]?x:fa[x]=getfa(fa[x]); }
int main(){
	cin>>n>>m; int s=0; For(i,1,m) { int x,y,z; cin>>x>>y>>z; s^=z; ed[++ecnt]={x,y,z}; S[x].insert(y),S[y].insert(x); }
	sort(ed+1,ed+1+ecnt); list<int> lis; For(i,1,n) lis.push_back(i);
	while(lis.size()){
		ccnt++; queue<int> q; q.push(lis.front()); lis.pop_front();
		while(q.size()){
			int u=q.front(); q.pop(); cp[u]=ccnt;
			for(auto it=lis.begin();it!=lis.end();){
				if(!S[u].count(*it)) q.push(*it),lis.erase(it++); else it++;
			}
		}
	}
	For(u,1,n) { siz[cp[u]]++; for(int v:S[u]) if(cp[v]==cp[u]) ec[cp[u]]++; }
	ll ans=0; int add=s; iota(fa+1,fa+1+ccnt,1);
	For(i,1,ecnt){
		int x=getfa(cp[ed[i].x]),y=getfa(cp[ed[i].y]); if(x!=y) flg[i]=1,ans+=ed[i].z,fa[y]=x;
	}
	For(i,1,ccnt) if(ec[i]/2+siz[i]-1!=1ll*siz[i]*(siz[i]-1)/2) cout<<ans<<'\n',exit(0);
	iota(fa+1,fa+1+n,1); For(i,1,ecnt) if(flg[i]) fa[getfa(ed[i].y)]=getfa(ed[i].x);
	For(i,1,ecnt) if(ed[i].z<add) {
		int x=ed[i].x,y=ed[i].y; if(getfa(x)!=getfa(y)) { add=ed[i].z; break; }
	}
	cout<<ans+add<<'\n';
	return 0;
}


posted @ 2023-07-15 08:19  CharlieVinnie  阅读(11)  评论(0编辑  收藏  举报