zoukankan      html  css  js  c++  java
  • 【国家集训队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


  • 相关阅读:
    数据处理
    Linux常用命令
    三大特征--多态
    封装设计思想--继承
    容器:列表、元组、字典
    封装
    python面向对象,类和对象
    python参数
    js中if条件语句以及switch条件语句的使用
    js中class类的基本理解及相关知识(一)
  • 原文地址:https://www.cnblogs.com/Eleven-Qian-Shan/p/13368602.html
Copyright © 2011-2022 走看看