zoukankan      html  css  js  c++  java
  • 「SOL」The Hanged Man(HDU)

    看到课程标题是 “DP优化”,以为就是斜率优化、单调性之类的……
    结果来了些什么奇妙操作


    # 题面

    给定一棵 (n) 个点的树和整数 (m),每个点有两类权值 (a_i,b_i)

    对于每个 (h=1sim m),求树上的一个独立集 (S),使得 (sumlimits_{iin S}a_ile h),且 (sumlimits_{iin S}b_i) 最大,输出满足上述条件的 (S) 的数量。

    数据规模:(nle50,mle5000,1le a_ile m,b_ile 10^6)


    # 解析

    题面就描述了一个背包问题,另外有独立集的限制。

    如果在原树上直接背包,需要背包合并(由于物品大小并非 (1),虽然两个点只会在 LCA 处产生贡献,但是合并的复杂度仍然是 (mathcal{O}(m^2)) 的),复杂度只能是 (mathcal{O}(nm^2)),无法再优化。

    话不多说,我们直接来看看怎么用神仙操作来做这道题。

    首先我们建出原树的点分树,点分树有两个(重要的)性质,其中一条我们非常熟悉:

    • 树高是 (mathcal{O}(log n))

    另外一条非常显然但是不常用(至少我没见过哪道题用):

    • 原树上与 (u) 相邻的点在点分树上只可能是 (mathbf{u}) 的祖先(u) 的后继。

    怎么利用这两点来解题?我们考虑按照点分树的 DFS 序 依次决策每个节点是否在独立集中。

    这样的话,当我们决策 (u) 时,就不需要考虑 (u) 的后继是否选入独立集,而只需要考虑 (mathbf{u}) 的祖先 有哪些被选入了独立集中 —— (u) 的祖先不多,只有 (mathcal{O}(log n)) 个,于是可以状压(f_{u,s,i}) 表示的状态为「已经决策了 (u),从根到 (u) 的链上被选入独立集的点是 (s)(压缩状态),且当前背包中装了总重为 (i) 的物品」,需要维护对应的物品价值最大值以及方案数。

    那么 (s) 的范围是 (mathcal{O}(2^{log n})=mathcal{O}(n)) 的,(f_{u,s,i}) 的状态数是 (mathcal{O}(n^2m)) 的。由于是 0-1 背包,可以 (mathcal{O}(1)) 转移,总的复杂度也是 (mathcal{O}(n^2m)) 的。

    下面关于一些细节简单说一下:

    • 知道了祖先的选定状态 (s),如何判断 (u) 是否能加入当前的独立集中?为了保持复杂度必须 (mathcal{O}(1)) 判断:
      • 维护压缩状态 lnk[u] 表示当前点分树的祖先中,哪些是原树上与 (u) 相连的节点。
      • 判断只需要判断 lnk[u](s) 是否有交;
      • 维护只需要在 DFS 进入点 (u) 时枚举原树上与 (u) 相邻的点 (v),更新 lnk[v],DFS 退出点 (u) 时在 lnk 中撤销 (u) 的信息即可。
    • (f_{u,s,i}) 中压缩状态 (s) 的维护:记 (u) 在 DFS 序上的前驱为 (v),我们需要从 (f_v) 转移到 (f_u)
      • 只需要深度小于 (dep_u) 的祖先;
      • 就需要把 (f_{v,s,i})(s) 中深度大于等于 (dep_u) 的部分删去;
      • 具体的,在 DFS 退出点 (u) 时,对每个 (sge 2^{dep_u}),将 (f_{u,s,i}) 贡献到 (f_{u,s-2^{dep_u},i})
      • 这样的效果就是递归到当前层时,有用的 (s) 的位数不超过 (dep_v-1)

    大概是一个不错的处理独立集的方法……


    # 源代码

    /*Lucky_Glass*/
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    
    inline int rin(int &r){
        int b=1,c=getchar();r=0;
        while(c<'0' || '9'<c) b=c=='-'?-1:b,c=getchar();
        while('0'<=c && c<='9') r=(r<<1)+(r<<3)+(c^'0'),c=getchar();
        return r*=b;
    }
    const int N=55,M=5005;
    #define con(type) const type &
    
    struct Data{
        int key;long long cnt;
        friend void maxc(Data &a,con(Data)b){
            if(a.key<b.key) a=b;
            else if(a.key==b.key) a.cnt+=b.cnt;
        }
        Data operator +(con(int)d)const{return (Data){key+d,cnt};}
    }f[2][64][M];
    struct Graph{
        int head[N],to[N<<1],nxt[N<<1],ncnt;
        void init(con(int)sz){for(int i=1;i<=sz;i++)head[i]=0;ncnt=0;}
        void addEdge(con(int)u,con(int)v,con(bool)typ=true){
            ncnt++;
            to[ncnt]=v,nxt[ncnt]=head[u],head[u]=ncnt;
            if(typ) ncnt++,to[ncnt]=u,nxt[ncnt]=head[v],head[v]=ncnt;
        }
        inline int operator [](con(int)u){return head[u];}
    }gr;
    
    int ncas,n,m,r_wei,r_root,r_tot,bagsiz,ndfn;
    int ara[N],arb[N],nxtto[N];
    bool ban[N];
    
    int toggleSize(con(int)u,con(int)fa){
        int sizu=1;
        for(int it=gr[u];it;it=gr.nxt[it])
            if(gr.to[it]!=fa && !ban[gr.to[it]])
                sizu+=toggleSize(gr.to[it],u);
        return sizu;
    }
    int findCenter(con(int)u,con(int)fa){
        int sizu=1,mxv=0;
        for(int it=gr[u];it;it=gr.nxt[it]){
            int v=gr.to[it],sizv;
            if(v==fa || ban[v]) continue;
            sizv=findCenter(v,u);
            sizu+=sizv,mxv=max(mxv,sizv);
        }
        mxv=max(mxv,r_tot-sizu);
        if(r_wei>mxv) r_wei=mxv,r_root=u;
        return sizu;
    }
    void dacDFS(con(int)u,con(int)dep){
        // # Marks
        for(int it=gr[u];it;it=gr.nxt[it]) nxtto[gr.to[it]]|=1<<dep;
        ban[u]=true;
        // # DP
        int I=(++ndfn)&1;
        for(int s=0,ss=1<<(dep+1);s<ss;s++)
            for(int i=0,ii=m;i<=ii;i++)
                f[I][s][i].key=-1,f[I][s][i].cnt=0;
        if(dep){
            for(int s=0,ss=1<<dep;s<ss;s++)
                for(int i=0;i<=bagsiz;i++)
                    f[I][s][i]=f[!I][s][i];
            int ii=min(bagsiz,m-ara[u]);
            for(int s=0,ss=1<<dep;s<ss;s++){
                if(nxtto[u]&s) continue;
                for(int i=0;i<=ii;i++)
                    if(~f[!I][s][i].key)
                        maxc(f[I][s|(1<<dep)][i+ara[u]],f[!I][s][i]+arb[u]);
            }
        }
        else{
            f[I][0][0].key=0,f[I][0][0].cnt=1;
            f[I][1][ara[u]].key=arb[u],f[I][1][ara[u]].cnt=1;
        }
        bagsiz=min(m,bagsiz+ara[u]);
        // # Transport
        for(int it=gr[u];it;it=gr.nxt[it]){
            int v=gr.to[it],sizv;
            if(ban[v]) continue;
            sizv=toggleSize(v,0);
            r_tot=sizv,r_wei=n+1,findCenter(v,0);
            int rtv=r_root;
            // printf("%d -> %d
    ",u,v);
            dacDFS(rtv,dep+1);
        }
        // # Roll-back
        for(int it=gr[u];it;it=gr.nxt[it]) nxtto[gr.to[it]]^=1<<dep;
        ban[u]=false;
        // # Toggle-DP
        I=ndfn&1;
        for(int s=1<<dep,ss=1<<(dep+1);s<ss;s++)
            for(int i=0;i<=bagsiz;i++)
                maxc(f[I][s^(1<<dep)][i],f[I][s][i]);
    }
    int main(){
        rin(ncas);
        for(int cas=1;cas<=ncas;cas++){
            rin(n),rin(m);
            // # Clear
            gr.init(n);
            bagsiz=ndfn=0;
            // # Read & Init-build
            for(int i=1;i<=n;i++) rin(ara[i]),rin(arb[i]);
            for(int i=1,u,v;i<n;i++) gr.addEdge(rin(u),rin(v));
            // # Calculate
            r_tot=n,r_wei=n+1,findCenter(1,0);
            int rt=r_root;
            dacDFS(rt,0);
            // # Print
            printf("Case %d:
    ",cas);
            int I=ndfn&1;
            for(int i=1;i<=m;i++) printf("%lld%c",f[I][0][i].cnt,i==m?'
    ':' ');
        }
        return 0;
    }
    

    THE END

    Thanks for reading!

    谁留下 烽火连天照夜的勾划
    又是谁在传说中寻他
    千年后 挥兵破阵 不过一刹那
    血雨腥风吻过了伤疤

    ——《山河令》By 忘川风华录 / 星尘

    > Link 山河令-网易云

    欢迎转载٩(๑❛ᴗ❛๑)۶,请在转载文章末尾附上原博文网址~
  • 相关阅读:
    简单的抽奖程序
    WinForm——记住密码
    《Effective C#中文版:改善C#程序的50种方法》读书笔记
    初入博客,给自己的学习定一个目标。
    做IT博客的第一天哈哈
    多表关联查询加内联形式收藏
    UpdateProgress的AssociatedUpdatePanelID属性增加后依然不显示的问题解决(转载)
    SQL Server2005安装找不到SQL Server Management Studio解决办法
    Access中not in优化方式
    App.Config文件中包含中文汉字程序出错的解决办法
  • 原文地址:https://www.cnblogs.com/LuckyGlass-blog/p/14477605.html
Copyright © 2011-2022 走看看