有线电视网
某收费有线电视网计划转播一场重要的足球比赛。他们的转播网和用户终端构成一棵树状结构,这棵树的根结点位于足球比赛的现场,树叶为各个用户终端,其他中转站为该树的内部节点。从转播站到转播站以及从转播站到所有用户终端的信号传输费用都是已知的,一场转播的总费用等于传输信号的费用总和。现在每个用户都准备了一笔费用想观看这场精彩的足球比赛,有线电视网有权决定给哪些用户提供信号而不给哪些用户提供信号。写一个程序找出一个方案使得有线电视网在不亏本的情况下使观看转播的用户尽可能多。2≤N≤3000。
关于树形dp,有一个经典套路,就是n^3动归,从下到上枚举每个点,每个点下的孩子和一个值。这道题也不例外。设计状态\(dp[k][i][j]\)为,对于第i个点,用前k个儿子,满足j个客户的最大收益。于是状态转移方程就是:\(dp[k][i][j]=max(dp[k-1][i][p]+dp[k][i][j-p]\ (0\le p\le j)\)。观察一下这个状态和方程,它是一个多决策最优化问题,满足最优化原理,且无后效性。同时k是可以被滚动数组压缩的,从后往前循环枚举p即可。
#include <cstdio>
using namespace std;
const int maxn=3005, maxm=3005, INF=1e9;
struct Graph{
struct Edge{
int to, next, v; Graph *bel;
inline int operator *(){ return to; }
Edge& operator ++(){
return *this=bel->edge[next]; }
};
void addedge(int x, int y, int v){
Edge &e=edge[++cntedge];
e.to=y; e.next=fir[x]; e.v=v;
e.bel=this; fir[x]=cntedge;
}
Edge& getlink(int x){
return edge[fir[x]]; }
Edge edge[maxm*2];
int cntedge, fir[maxn];
}g;
int n, m, size[maxn];
int dp[maxn][maxn];
inline int max(int x, int y){
return x<y?y:x; }
void predfs(int now, int par){
Graph::Edge e=g.getlink(now);
for (; *e; ++e){
if (*e==par) continue;
predfs(*e, now);
size[now]+=size[*e];
}
if (size[now]==0) size[now]=1;
}
void dfs(int now, int par){
Graph::Edge e=g.getlink(now);
dp[now][0]=0; int cntsize=0;
for (; *e; ++e){ //枚举孩子
if (*e==par) continue; dfs(*e, now);
cntsize+=size[*e];
for (int j=cntsize; j>=0; --j) //总客户数
for (int p=cntsize-size[*e]; p>=0; --p) //前面的孩子中有几个客户
dp[now][j]=max(dp[now][j],
dp[now][p]+dp[*e][j-p]-((j-p)?e.v:0));
}
}
void init(){
for (int i=0; i<maxn; ++i)
for (int j=0; j<maxn; ++j)
dp[i][j]=-INF;
}
int main(){
init();
scanf("%d%d", &n, &m); int k, a, c;
for (int i=1; i<=n-m; ++i){
scanf("%d", &k);
for (int j=1; j<=k; ++j){
scanf("%d%d", &a, &c);
g.addedge(i, a, c);
}
}
for (int i=n-m+1; i<=n; ++i) scanf("%d", &dp[i][1]);
predfs(1, 0); dfs(1, 0); int pos;
for (pos=size[1]; pos>=0; --pos)
if (dp[1][pos]>=0) break;
printf("%d", pos); return 0;
}