• 2067: [Poi2004]SZN——树上贪心+二分


    题目大意:

    给一棵树。求用最少的链覆盖这棵树(链不能相交),在这个基础上求最长的链最短可以是多少。

    n<=10000

    题解:

    肯定先处理第一问:

    答案:$sum_(du[i]-1)/2+1$

    证明:

    1.对于一个非根的节点x,x的每一个到儿子的边必须被覆盖。

    只有两种可能:要么这个链不超过x,要么从x头上进来。

    发现,从x头上进来的链有且只有一个。

    如果x的儿子数量是偶数,肯定只能把边两两配对。如果奇数,那么剩下这一个就和从x头上下来的链在一起即可。

    儿子数du[i]-1,花费:(du[i]-1)/2

    2.对于根节点rt

    儿子偶数的话,同理,如果儿子是奇数的话,由于rt没有从头上下来的链,只能单独算作一个链。

    所以答案是:(du[i]+1)/2,即(du[i]-1)/2+1

    证毕。

    第二问:

    二分答案显然。

    判定mid:我们既要满足最长小于等于mid,还要满足贪心方法依然能使得链数最小。

    根据刚才第一问的分析,子树x的对于儿子的覆盖情况无非就那么两种。而且x内的覆盖情况对其他子树的影响只有从x上去的那一条链。

    而比较麻烦的是x头上下来的链。发现,这个链只有一个。

    所以,我们在x子树内合法覆盖、用的链最少的前提下,想头上的链越短越有利。

    f[x]表示 ,x头上的链的最短长度。

    或者更准确地说,x头上的链进入x子树后,还要延伸多少。

    dfs时,

    对于x,我们先求出f[son]

    然后想办法求f[x]

    把f[son]+1计入mem数组,然后从小到大排序。

    如果x有奇数个儿子,二分f[x]位置,然后大小配对。

    如果x有偶数个儿子,如果可以大小直接配对,完事大吉,f[x]=0,然后回溯。

    否则,二分f[x]位置,然后把剩下的最大的f[son]+1独自一条链(这样也可以满足方案数最小的)

    继续大小配对判断。

    当然,对于根节点,f[x]没有意义了,就检查一下,如果偶数个儿子,直接配对。奇数个,去掉最大的配对。

    任何时候,如果f[son]+1>mid,或者f[x]不存在,或者根节点匹配失败,都return false

    所以,其实是大二分,然后dfs贪心的时候再二分。

    代码:

    注意两个mid不要弄混。。。。

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const int N=10000+5;
    int n;
    struct node{
        int nxt,to;
    }e[2*N];
    int hd[N],cnt;
    void add(int x,int y){
        e[++cnt].nxt=hd[x];
        e[cnt].to=y;
        hd[x]=cnt;
    }
    int son[N],tot;
    int mid;
    int ans1,ans2;
    int du[N];
    bool fl;
    int f[N];
    void dfs(int x,int fa){
        //cout<<x<<" "<<fa<<endl;
        if(!fl) return;//warning!!!
        for(int i=hd[x];i;i=e[i].nxt){
            int y=e[i].to;
            if(y==fa) continue;
            dfs(y,x);
        }
        tot=0;
        if(!fl) return;//warning!!
        for(int i=hd[x];i;i=e[i].nxt){
            int y=e[i].to;
            if(y==fa) continue;
            son[++tot]=f[y]+1;
        }
        sort(son+1,son+tot+1);
        //cout<<x<<" 's son "<<endl;
        //for(int i=1;i<=tot;i++) cout<<son[i]<<" ";cout<<endl;
        if(son[tot]>mid) {fl=false;return;}//warning!!!
        if(x==1){
            bool can=true;
            if(tot%2==1) tot--;
            for(int i=1;i<=tot/2;i++){
                if(son[i]+son[tot-i+1]>mid){
                    can=false;break;
                }
            }
            if(!can) fl=false;
        }
        else{
            if(tot%2==0){
                bool can=true;
                for(int i=1;i<=tot/2;i++){
                    if(son[i]+son[tot-i+1]>mid){
                        can=false;break;
                    }
                }
                if(can) {
                    f[x]=0;return;//warning!!!!! has returned
                }
            }
            f[x]=-1;//warning!!!
            int L=1,R=tot;
            while(L<=R){
                int M=(L+R)>>1;
                int up=tot;
                if(M==tot) up--;
                if(tot%2==0) up--;
                bool can=true;
                for(int i=1;i<=up;i++){
                    if(i==M) continue;
                    if(son[i]+son[up]>mid) {
                        can=false;break;
                    }
                    up--;
                } 
                //cout<<x<<" M "<<M<<" : "<<can<<endl;
                if(can) f[x]=son[M],R=M-1;
                else L=M+1;
            }
            if(f[x]==-1) {
                fl=false;
            }
        }
    }
    bool che(){
        fl=true;
        //cout<<" mid "<<mid<<" ------------------"<<endl;
        memset(f,0,sizeof f);
        dfs(1,0);
        //cout<<" ff "<<endl;
        //for(int i=1;i<=n;i++){
        /// cout<<i<<" : "<<f[i]<<endl;
        //}
        return fl;
    }
    int main(){
        scanf("%d",&n);int x,y;
        for(int i=1;i<=n-1;i++){
            scanf("%d%d",&x,&y);add(x,y);add(y,x);
            du[x]++,du[y]++;
        }
        for(int i=1;i<=n;i++){
            ans1+=(du[i]-1)/2;
        }
        ans1++;
        int l=1,r=n-1;
        while(l<=r){
            mid=(l+r)>>1;
            if(che()){
                ans2=mid,r=mid-1;
            }
            else l=mid+1;
        }
        printf("%d %d",ans1,ans2);
        return 0;
    }

    总结:

    树上贪心肯定和儿子有关系。

    观察覆盖x到儿子的边的两种方案。结合二分贪心


    $upda:2018.12.29$

    NOIP考到了这个题目,几乎是原题,而且没有第一问。。。。

    大致的思路是对的。

    但是二分里面的二分没有写上,,(反而写了儿子个数平方套set?)

    看来写题目不光是思路要有,关键的trick也不能放过

    (对了感谢ywy_c_asm神犇,多亏了他给我推荐SZN这个题,否则NOIP就GG了)

  • 相关阅读:
    C#中WinForm程序退出方法技巧(转载)
    webbrowser访问网站禁止弹窗
    (转载)ASP.NET三大核心对象及基础功能解析
    webBrowser调用外部js文件和js函数(转载)
    java IO流
    java线程状态,优先级
    java线程
    Collection接口,Map接口
    序列化和反序列化
    JsonUtil自定义
  • 原文地址:https://www.cnblogs.com/Miracevin/p/9799344.html
Copyright © 2020-2023  润新知