题解 P1273 【有线电视网】
首发于博客园Santiego
干透一道题
很多题解提到的dp方程都没有详细说明遍历顺序及dp方程的转移等,直接抛出一个被优化过一维的二维dp转移方程,本蒟蒻在此想要再补充一下。
这道题本质就是个背包。这道题dp有点奇怪,最终答案并不是dp值,而是最后遍历寻找那个合法且最优的
因为dp维护的是成本,并且按照背包思想,存在让这个用户接入和不让这个用户接入两种决策,类比背包,所以容易得到状态转移原始方程:
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;
}
需要注意:
- 链式前向星边数不是
N ,一定要开大 - dp的遍历顺序一定要正确,因为你已经优化了一维
i - 用户端支付的钱
w 应该表示为dp[n][1]=w - dp一定要结合dp方程初始化