CF1508C. Complete the MST

有个完全图,其中\(m\)条边的权值已经被确定。现在要确定其它边的权值,要求最终所有权值异或和为\(0\)。最小化权值和。

\(n,m\le 2*10^5\)


称没有被确定权值的边为零边。设已经被确定的权值的异或和为\(W\)

无视限制,把零边的边权设为\(0\),然后跑MST。如果存在零边不在MST内,那就把这条零边的权值设为\(W\)。显然此时最优。如果\(W=0\),显然此时也最优。

否则:零边全在MST内,\(W\neq 0\)。以下证明,存在一种方案,只有一个零边权值为\(W\),其它权值为\(0\),此时最优。

假设存在种最优的不满足这个条件的对零边赋值的方案,跑MST。如果存在一个零边不在MST内,则令其它的零边权值为\(0\),它的权值为\(W\),答案不会更劣;否则,任选一条零边,其它零边权值为\(0\),它的权值为\(W\),答案不变。

做法分为两部分:1. 对零边建出一棵生成森林。2. 枚举权值为\(W\)的零边,计算MST。

第一部分:

我的做法:用并查集维护连通块。枚举点\(x\)\(x\)要和\(O(deg_x)\)个编号区间连边。维护\(nxt_i\)表示\([i,nxt_i]\)都在一个连通块内,也可以用并查集来维护。现在给某个编号区间连边,把这个区间里原来在不同连通块内的各个区间分别取个点连,然后更新\(nxt\)。这样时间是\(O(m)\)(并查集时间忽略)的。

dyp的做法:对于每个连通块维护个线段树,线段树节点记编号区间中是否存在点不在连通块内。每次遍历线段树,找到一个不在连通块内的点,然后进行线段树合并。时间\(O(m\lg n)\)。可以优化:枚举起点,维护个链表,这个链表中存下不在和起点同连通块内的点。设当前点为\(x\),遍历链表,对于和\(x\)不相邻的点,将其加入连通块并从链表中删去,并把这个点加入队列(或dfs)。时间\(O(m)\)

gmh77的做法:找到度数最小的点\(r\),用它暴力连边。接下来和它不在同个连通块内的点的个数为\(deg_r\)\(deg_r\le \frac{2m}{n}\)。对于这\(deg_r\)个点分别暴力连边,于是连了\(O(\frac{2m}{n} n)\)条边,就是\(O(m)\)的了。

第二部分:

我的做法:如果生成森林中没有用到所有的零边,答案已经确定;否则,\(m+cnt_{零边}=\frac{n(n-1)}{2}\),所以\(n=O(\sqrt m)\)。枚举\(O(n)\)个零边作为\(W\),暴力跑MST,朴素实现\(O(nm)\)。优化:实际上可以先对确定边跑MST,用在MST中的边跑这里的MST,时间\(O(n^2)\)。于是时间是\(O(\min(n^2,m))\)

dyph和gmh77的做法:考虑计算权值为\(W\)的边不在最终的MST的情况。对确定边跑MST,合并零边和确定边的MST。如果有某个确定边,它在确定边的MST中而不在零边和确定边的MST中,说明存在一个零边将其替换,换句话说也可以用它来替换这个零边。于是找到这样的边的最小边权,用它替换权值为\(W\)的邻边。


using namespace std;
#include <bits/stdc++.h>
#define N 200005
#define ll long long
int n,m;
int xo;
int deg[N];
struct edge{int u,v,w;} ed[N+N];
bool cmped(edge x,edge y){return x.w<y.w;}
vector<int> nei[N];
set<pair<int,int> > bz;
edge ep[N];
int cnt;
int dsu[N];
int getdsu(int x){return dsu[x]==x?x:dsu[x]=getdsu(dsu[x]);}
ll MST(int ban){
	ll ans=0;
	for (int i=1;i<=n;++i)
		dsu[i]=i;
	for (int i=1;i<=cnt;++i){
		if (i==ban) continue;
		int u=getdsu(ep[i].u),v=getdsu(ep[i].v);
		if (u!=v)
			dsu[u]=v;
	}
	if (ban && xo<=ed[1].w){
		int u=getdsu(ep[ban].u),v=getdsu(ep[ban].v);
		if (u!=v)
			dsu[u]=v,ans+=xo;
	}
	for (int i=1;i<=m;++i){
		int u=getdsu(ed[i].u),v=getdsu(ed[i].v);
		if (u!=v)
			dsu[u]=v,ans+=ed[i].w;
		if (ban && ed[i].w<xo && (i==m || xo<=ed[i+1].w)){
			int u=getdsu(ep[ban].u),v=getdsu(ep[ban].v);
			if (u!=v)
				dsu[u]=v,ans+=xo;
		}
	}
	return ans;
}
int nxt[N];
int getnxt(int x){return x==nxt[x]?x:nxt[x]=getnxt(nxt[x]);}
void merge(int l,int r,int c){
	if (l>r) return;
	if (l<=c && c<=r){
		merge(l,c-1,c);
		merge(c+1,r,c);
		return;
	}
	if (getdsu(l)!=getdsu(c)){
		dsu[getdsu(l)]=getdsu(c);
		if (bz.find(make_pair(l,c))==bz.end())
			bz.insert(make_pair(c,l)),ep[++cnt]={c,l,0};
	}
	while (getnxt(l)<r){
		int x=getnxt(l)+1;
		nxt[x-1]=x;
		if (getdsu(x)!=getdsu(c)){
			dsu[getdsu(x)]=getdsu(c);
			if (bz.find(make_pair(x,c))==bz.end())
				bz.insert(make_pair(c,x)),ep[++cnt]={c,x,0};
		}
	}
}
int main(){
	scanf("%d%d",&n,&m);
	if (m==0){
		printf("0\n");
		return 0;
	}
	for (int i=1;i<=m;++i){
		int u,v,w;
		scanf("%d%d%d",&u,&v,&w);
		ed[i]={u,v,w};
		xo^=w;
		deg[u]++,deg[v]++;
		nei[u].push_back(v);
		nei[v].push_back(u);
	}
	sort(ed+1,ed+m+1,cmped);
	for (int i=1;i<=n;++i)
		dsu[i]=i,nxt[i]=i;
	nxt[n+1]=n+1;
	for (int i=1;i<=n;++i){
		if (deg[i]==n-1) continue;
		sort(nei[i].begin(),nei[i].end());
		if (deg[i]>0){
			merge(1,nei[i][0]-1,i);
			merge(nei[i][deg[i]-1]+1,n,i);
			for (int j=0;j<deg[i]-1;++j)
				merge(nei[i][j]+1,nei[i][j+1]-1,i);
		}
		else
			merge(1,n,i);
	}
	if (cnt+m<(ll)n*(n-1)/2){
		ll ans=MST(0);
		printf("%lld\n",ans);
		return 0;
	}
	ll ans=LLONG_MAX;
	for (int i=1;i<=cnt;++i)
		ans=min(ans,MST(i));
	printf("%lld\n",ans);
	return 0;
}
posted @ 2021-04-19 22:34  jz_597  阅读(242)  评论(0编辑  收藏  举报