CodeForces 1023F Mobile Phone Network(并查集 + 最小生成树)

大致题意:你和你的竞争对手一起竞争去铺设一个网络,这个网络有n个节点。然后你的竞争对手已经给出了他的方案。他给出了他要建立的所有连线以及每根连线的代价。然后你已经决定了你的连线方案,但是每根线的定价还没有确定。当你给出定价之后,客户会在你们两个提供的所有连线中选择n-1条线,使得网络连通并且代价最小。现在,你想要让客户把你提供的所有线路都选上,问你该如何定价使得在满足你所有的线路都被选中的同时,自己的收益最大,也即定价总和最高。

当时比赛的时候没有时间看这道题,赛后自己想了想就知道了做法。但是看了别人的代码,发现别人方法虽然和我一样,但是实现的方式却是如此的巧妙。

扫描二维码关注公众号,回复: 2865488 查看本文章

首先,既然自己的所有线路都要被选中,那么我不妨就直接按照kruskal的方法,直接先把自己的所有边给合并添加到并查集中。然后,我们看到题目已经帮了我们的忙,你的竞争对手的线路给出来的时候已经按照代价排好了序。那么我们自然而然的也尝试把竞争对手的线路也加入到并查集中合并。对于可以成功合并的,我们直接合并,对于不能合并的,我们记录下来。

现在我们考虑一下这些不能合并的该如何处理。不能合并,意味着如果我选择了这些边,会构成环。为了不构成环,我就要让那些加入之后在环上的边的权值小于等于这个不能加入的边。而为了这个定价最高,我显然要把这些边定价为这个不能加入的边的代价。这样我们发现,问题就变成了把两点之间的路径设定为某个数字,而且是只能变小,不能变大。如此,这就是一个在最小生成树上面的一个链修改操作的题目。于是我就想着用dfs序、线段树、树链剖分什么的……

然而,看了别人代码之后我发现,一个并查集足矣。对于两个点,如果有一条非树边要连接它们,那么这两点路径上的点的权值都要赋值为这条非树边的权值。这时,我们还是用到了题目给定的性质,即竞争对手的边的给定是已经排好序的,那么也就意味着,如果存在下一条非树边还要修改刚刚修改过的边的权值,那么这些权值一定不会被修改,因为权值只能变小,不能变大。因此,等于说一条边或点如果被修改了,那么它以后就不会再被修改了,所以这些边我们甚至可以删除。而大部分人就是这么做的,对于一条非树边,在修改的过程中,我同时把所有的点向父亲合并,那么最后路径上的所有点就变成了LCA这一个点。以后再次遇到的时候,其他的点就不会被修改。这样,每个点最多被修改一次,时间复杂度就是O(N)的。

完了之后,我们再统计最后的和就行了。然后关于输出-1,如果我发先一条我自己的线路到最后都没有与其父亲合并,也即到最后这条边都没有权值的上界,那么这条边的代价就是可以取无穷大,此时直接输出-1即可。具体见代码:

#include<cstdio>
const int maxn=500010,maxm=500010;
int n,k,m,fa[maxn];
int find(int i){return fa[i]==i?i:fa[i]=find(fa[i]);}
struct edge{int to,ty;edge*next;}E[maxn*2],*ne=E,*fir[maxn];
void link(int u,int v,int ty){*ne=(edge){v,ty,fir[u]};fir[u]=ne++;}
void link2(int u,int v,int ty){link(u,v,ty);link(v,u,ty);}
int a[maxm],b[maxm],val[maxm],cnt;
int f[maxn],d[maxn],res[maxn];
void dfs(int i){
	for(edge*e=fir[i];e;e=e->next)if(e->to!=f[i]){
		f[e->to]=i;
		d[e->to]=d[i]+1;
		dfs(e->to);
	}
}
int main(){
	scanf("%d%d%d",&n,&k,&m);
	for(int i=1;i<=n;i++)fa[i]=i;
	while(k--){
		int u,v;scanf("%d%d",&u,&v);
		fa[find(u)]=find(v);
		link2(u,v,0);
	}
	while(m--){
		int u,v,w;scanf("%d%d%d",&u,&v,&w);
		if(find(u)==find(v)){
			a[cnt]=u;b[cnt]=v;val[cnt++]=w;
		}
		else fa[find(u)]=find(v),link2(u,v,1);
	}
	dfs(1);
	for(int i=1;i<=n;i++)fa[i]=i;
	for(int i=0;i<cnt;i++){
		int u=find(a[i]),v=find(b[i]);
		while(u!=v){
			if(d[u]>d[v])res[u]=val[i],fa[u]=find(f[u]),u=fa[u];
			else
            {
                res[v]=val[i];
                fa[v]=find(f[v]);
                v=fa[v];
            }
		}
	}
	long long ans=0;
	for(int i=1;i<=n;i++)
		for(edge*e=fir[i];e;e=e->next)
			if(e->to==f[i]&&!e->ty){
				if(fa[i]==i)return puts("-1"),0;
				ans+=res[i];
			}
	printf("%lld\n",ans);
}

猜你喜欢

转载自blog.csdn.net/u013534123/article/details/81810926