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()
表示并查集的代表元。分两种情况讨论:
-
若 \(x \neq y\),那么如果 \(d[x] \mathrm{or} d[y] =1\),说明 \(x\) 和 \(y\),至少有一个不是基环树,可以合成一棵树或基环树。那么我们加上这条边的边权(公主的嫁妆)。这时我们还要更新新树的\(d=d[x] \mathrm{and} d[y]\),大家稍微思考一下即可。
-
若 \(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;
}