cunzai_zsy0531

关注我

CF875F Royal Questions 题解

Post time: 2020-09-07 19:33:53

题目链接 Link

首先我们考虑建边,如果我们把某一个公主的两个潜在丈夫连一条边,边权为 \(w_i\),如果选了某一条边,就相当于是选了一个公主。

接下来我们考虑什么情况下我们的选边是合法的。首先考虑树的情况,如果我们选择了 \(k\) 条边连接了 \(k+1\) 个顶点,那么我们可以舍弃一个点(一个王子),那么剩下 \(k\) 个王子可以完全匹配这 \(k\) 个公主。

那么我们继续思考,如果在这棵树中增加一条边呢?这样是 \(k+1\) 个王子和 \(k+1\) 个公主,那么他们能够完全匹配吗?答案是肯定的。对于这样一棵基环树(就是上面讲的点数\(=\)边数的东西),我们把其中唯一的那个环作为根节点,因为这个环中,王子和公主可以匹配,所以我们把它看成根节点,剩下的边(公主)都和儿子点(王子)匹配,这样就可以完成了。

我们可以使用 \(Kruskal\) 算法解决这个问题(可以理解为边权和最大的基环树或树组成的森林)。我们在普通的 Kruskal 的基础上再定义一个数组 \(d_i\) 表示以 \(i\) 为代表元的集合是否是一个基环树,\(1\) 表示无环, \(0\) 表示是一个基环树。显然,两个树可以合成一个树,一个树和一个基环树可以合成一个基环树。

我们设 x=find(e[i].u)y=find(e[i].v),其中 find() 表示并查集的代表元。分两种情况讨论:

  1. \(x \neq y\),那么如果 \(d[x] \mathrm{or} d[y] =1\),说明 \(x\)\(y\),至少有一个不是基环树,可以合成一棵树或基环树。那么我们加上这条边的边权(公主的嫁妆)。这时我们还要更新新树的\(d=d[x] \mathrm{and} d[y]\),大家稍微思考一下即可。

  2. \(x=y\),那么 \(d[x]\) 只能等于 \(0\),即树,加一条边成为了基环树,\(d[x]\) 变为 \(1\)

这样我们就可以使用修改版的 Kruskal 通过此题。

点击查看代码
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
inline void read(int &t){
	int res=0,flag=1;
	char c=getchar();
	while(!isdigit(c)){if(c=='-') flag=-1;c=getchar();}
	while(isdigit(c)) res=res*10+c-'0',c=getchar();
	t=res*flag;
}
const int N=2e5+13;
struct Edge{
	int u,v,w;
	bool operator <(const Edge &a)const{return w>a.w;}
}e[N];
int fa[N],d[N],n,m,sum;
int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);}
int main(){
	read(n),read(m);
	for(int i=1;i<=n;++i) fa[i]=i,d[i]=1;
	for(int i=1;i<=m;++i) scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].w);
	sort(e+1,e+m+1);//别忘了Kruskal是基于贪心思想,要排序。
    //下面就是修改后的Kruskal
	for(int i=1;i<=m;++i){
		int x=find(e[i].u),y=find(e[i].v);
		if(x!=y&&(d[x]||d[y])) fa[x]=y,sum+=e[i].w,d[y]=d[x]&d[y];//第一种情况
		else if(x==y&&d[x]) d[x]=0,sum+=e[i].w;//第二种情况
	}
	printf("%d\n",sum);//最终的答案就是累加的那些边权
	return 0;
}
posted @ 2022-04-21 14:43  cunzai_zsy0531  阅读(65)  评论(0编辑  收藏  举报