【国家集训队2】Tree I

【国家集训队2】Tree I

题目传送门

Preface 前言

一道题目十分简明但正解看起来并不容易想的题(是我太菜

思路来源自这篇博客,我这篇题解相当于一个详释版(?)


Algorithm 算法

二分答案(的一部分)\(&\) \(Kurskal\)求最小生成树


Solution 解决

首先,使用最小生成树的原因不用讲吧——题目明确要求“求出一棵最小权的树”

然后,来思考怎么处理本题的关键——最小生成树中的白边数量

  • 先对白边进行分类讨论
  1. 白边数量恰好为\(need\)

  2. 白边数量少于\(need\)

  3. 白边数量多于\(need\)

  • 再来对上述三种情况进行分析:
  1. 对于第一种最理想的情况,直接输出最小生成树的边权和即可

  2. 如果了,则说明白边的边权普遍较大,排在了黑边后面

  3. 如果了,则说明白边的边权普遍较小,排在了黑边前面

而我们需要做的,就是调节白边与黑边的顺序,使得最小生成树中恰好有\(need\)条白边

  • 那怎么调节呢?边权不是固定的吗?

边权是固定的,但是我们也能调节:

  1. 将所有白边的边权统一加上一个\(mid\)值(先不要在意变量名qwq)

  2. 在最终的最小生成树统计边权和时,将白边的边权和再减回去

当然,上面的两步只是我们最初的思路——人为改变白边的边权来调节顺序以解决问题

  • 问题又来了,我们怎么知道\(mid\)取多少呢?

(这时请注意变量名ovo) 正如\(mid\)这个变量的名字,我们可以使用二分来查找这个值

太玄学,还是要懂原因的:

  1. 我们每次枚举一个\(mid\),然后加边权后去跑最小生成树(现在的最小生成树不一定是最终的最小生成树)

  2. 跑完后判断是否存在\(need\)条白边,如果存在则说明当前的\(mid\)是合法的,我们就存储下来

  3. 因为要求(最终的)最小生成树的边权和最小,所以我们还要继续枚举\(mid\)

  4. 枚举这种做法就可以使用二分来大大提高效率

还要注意,因为要枚举多次,所以减边权操作是每次枚举一次就进行一次


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


posted @ 2020-07-23 20:35  Eleven谦  阅读(59)  评论(0编辑  收藏