题解 P1273 【有线电视网】

· · 题解

首发于博客园Santiego

干透一道题

很多题解提到的dp方程都没有详细说明遍历顺序及dp方程的转移等,直接抛出一个被优化过一维的二维dp转移方程,本蒟蒻在此想要再补充一下。

这道题本质就是个背包。这道题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方程: $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\n", i);
            break;
        }
    return 0;
}

需要注意: