zoukankan      html  css  js  c++  java
  • 洛谷 P1273 有线电视网(树形背包)

    洛谷 P1273 有线电视网(树形背包)

    干透一道题

    题面:洛谷 P1273

    本质就是个背包。这道题dp有点奇怪,最终答案并不是dp值,而是最后遍历寻找那个合法且最优的(i)作为答案。dp值存的是当前状态下的成本,所以合法情况即当成本值大于等于0,不亏本的时候。

    因为dp维护的是成本,并且按照背包思想,存在让这个用户接入和不让这个用户接入两种决策,类比背包,所以状态转移方程容易得到原始方程:

    [dp[s][i][j]=max { dp[s][i-1][j-k]+dp[w][size_w][k]-cost[s][w](w in son[s]) } ]

    (dp[s][i][j])表示当前节点(s)的前(i)个儿子中选取(j)个用户的成本,决策当前儿子接入(k)个用户。但是这里我们可以像01背包那样优化一维(i),只要我们递增遍历(i),递减遍历(j)即可,因为这样(i-1)轮的(dp[s][i-1][j-k])状态才没有被第(i)轮的(dp[s][i][j-k])状态覆盖,并且(dp[w][k])最终的值就是(dp[w][size_w][k])

    最终二维的dp方程:

    [dp[s][j]=max { dp[s][j-k]+dp[w][k]-cost[s][w](w in son[s]) } ]

    192ms AC Code:

    #include <cstdio>
    using namespace std;
    #define MAXN 3003
    #define MAX(A,B) ((A)>(B)?(A):(B))
    #define INF 0x3ffffff
    struct e{
        int w,v,nxt;
    } edge[10000010]; //边数一定要开大!
    int dp[MAXN][MAXN],head[MAXN],sz[MAXN];
    int n,m,cnt_e;
    inline void adde(int u, int v, int w){
        edge[++cnt_e].w=w;
        edge[cnt_e].v=v;
        edge[cnt_e].nxt=head[u];
        head[u]=cnt_e;
    }
    int solve(int x, int fa){
        if(x>=n-m+1&&x<=n) //是否为用户端
            return sz[x]=1;
        for(register int i=head[x];i;i=edge[i].nxt){ //”递增”遍历儿子i
            sz[x]+=solve(edge[i].v, x); //树形dp通常自下而上更新
            for(register int j=sz[x];j>=0;--j) //枚举状态
                for(register int k=0;k<=sz[edge[i].v];++k)  //枚举决策,当前儿子取几个用户
                    dp[x][j]=MAX(dp[x][j], dp[x][j-k]+dp[edge[i].v][k]-edge[i].w);
            //dp[s][i][j]=max{dp[s][i-1][j-k]+dp[w][size_w][k]-cost[s][w]} 原始状态转移方程
        }
        return sz[x];
    }
    int main() {
        scanf("%d %d", &n, &m);
        for(register int i=0;i<=n;i++)
            for(register int j=0;j<=m;j++)
                dp[i][j]=-INF;
        for (int i=1;i<=n;i++) dp[i][0]=0; //注意初始化!
        for(int i=1;i<=n-m;++i){
            int sz;
            scanf("%d", &sz);
            for(int j=1;j<=sz;++j){
                int a,c;
                scanf("%d %d", &a, &c);
                adde(i, a, c); //链式前向星建边
            }
        }
        for(int i=n-m+1;i<=n;++i)
            scanf("%d", &dp[i][1]);
        solve(1,0);
        for(register int i=m;i>=0;--i) //递减遍历找到第一个合法值
            if(dp[1][i]>=0){
                printf("%d
    ", i);
                break;
            }
        return 0;
    }
    

    需要注意:

    • 链式前向星边数不是(N),一定要开大
    • dp的遍历顺序一定要正确,因为你已经优化了一维(i)
    • 用户端支付的钱(w)应该表示为(dp[n][1]=w)
    • dp一定要结合dp方程初始化
  • 相关阅读:
    并发运行的思维模型
    进程和线程的区别
    拿来主义
    同步组件合作和团队合作 让世界变得更美好
    strace a++;b++;a+b;
    System 88: GDB Overview
    numpy多维数组维度及添加轴的理解
    Numpy入门
    python列表list 和numpy.array区别
    数组的生成方法
  • 原文地址:https://www.cnblogs.com/santiego/p/10198971.html
Copyright © 2011-2022 走看看