【国家集训队2】Tree I
【国家集训队2】Tree I
Preface 前言
一道题目十分简明但正解看起来并不容易想的题(是我太菜)
思路来源自这篇博客,我这篇题解相当于一个详释版(?)
Algorithm 算法
二分答案(的一部分)\(&\) \(Kurskal\)求最小生成树
Solution 解决
首先,使用最小生成树的原因不用讲吧——题目明确要求“求出一棵最小权的树”
然后,来思考怎么处理本题的关键——最小生成树中的白边数量
- 先对白边进行分类讨论:
-
白边数量恰好为\(need\)条
-
白边数量少于\(need\)条
-
白边数量多于\(need\)条
- 再来对上述三种情况进行分析:
-
对于第一种最理想的情况,直接输出最小生成树的边权和即可
-
如果少了,则说明白边的边权普遍较大,排在了黑边后面
-
如果多了,则说明白边的边权普遍较小,排在了黑边前面
而我们需要做的,就是调节白边与黑边的顺序,使得最小生成树中恰好有\(need\)条白边
- 那怎么调节呢?边权不是固定的吗?
边权是固定的,但是我们也能调节:
-
将所有白边的边权统一加上一个\(mid\)值(先不要在意变量名qwq)
-
在最终的最小生成树统计边权和时,将白边的边权和再减回去
当然,上面的两步只是我们最初的思路——人为改变白边的边权来调节顺序以解决问题
- 问题又来了,我们怎么知道\(mid\)取多少呢?
(这时请注意变量名ovo) 正如\(mid\)这个变量的名字,我们可以使用二分来查找这个值
太玄学,还是要懂原因的:
-
我们每次枚举一个\(mid\),然后加边权后去跑最小生成树(现在的最小生成树不一定是最终的最小生成树)
-
跑完后判断是否存在\(need\)条白边,如果存在则说明当前的\(mid\)是合法的,我们就存储下来
-
因为要求(最终的)最小生成树的边权和最小,所以我们还要继续枚举\(mid\)
-
而枚举这种做法就可以使用二分来大大提高效率!
还要注意,因为要枚举多次,所以减边权操作是每次枚举一次就进行一次
Code 代码
思路就是上述这么多啦,感觉分析得还是比较详细的,现在贴上代码
#include <bits/stdc++.h>
using namespace std;
int V,E,l=-100,r=100,ans,sum,need,flag;
int fa[520010];
struct node {
int u,v,w,col;
} e[520010];
inline int find_fa(int x) {
if(x==fa[x]) return x;
return fa[x]=find_fa(fa[x]);
}
inline bool cmp(node x,node y) { //注意排序要看两方面
if(x.w==y.w) return x.col<y.col;
return x.w<y.w;
}
inline int kurskal() {
ans=0;
int now=0,num=0;
for(register int i=1;i<=V;i++) fa[i]=i;
sort(e+1,e+1+E,cmp);
for(register int i=1;i<=E;i++) {
int x=find_fa(e[i].u);
int y=find_fa(e[i].v);
if(x!=y) {
fa[x]=y;
ans+=e[i].w;
if(e[i].col==0) num++; //num统计该最小生成树中的白边数量
now++;
}
if(now==V-1) break;
}
return num;
}
inline bool check(int x) {
for(register int i=1;i<=E;i++) { //进行加边权操作
if(e[i].col==0) e[i].w+=x;
}
flag=kurskal();
for(register int i=1;i<=E;i++) { //进行减边权操作
if(e[i].col==0) e[i].w-=x;
}
return flag>=need?true:false; //判断白边数量是否合法
}
int main() {
scanf("%d%d%d",&V,&E,&need);
for(register int i=1;i<=E;i++) {
scanf("%d%d%d%d",&e[i].u,&e[i].v,&e[i].w,&e[i].col);
e[i].u++;e[i].v++; //输入从0开始
}
while(l<=r) {
int mid=(l+r)>>1;
if(check(mid)) {
sum=mid; //sum存储最终的mid值
l=mid+1;
}
else r=mid-1;
}
check(sum); //额外进行一次是因为要算出最终的ans
printf("%d",ans-need*sum);
return 0;
}
最后,如果有任何问题欢迎dalao在下面留言,我会及时回复、改正,谢谢qwq