luogu P1273 有线电视网
题目描述
某收费有线电视网计划转播一场重要的足球比赛。他们的转播网和用户终端构成一棵树状结构,这棵树的根结点位于足球比赛的现场,树叶为各个用户终端,其他中转站为该树的内部节点。
从转播站到转播站以及从转播站到所有用户终端的信号传输费用都是已知的,一场转播的总费用等于传输信号的费用总和。
现在每个用户都准备了一笔费用想观看这场精彩的足球比赛,有线电视网有权决定给哪些用户提供信号而不给哪些用户提供信号。
写一个程序找出一个方案使得有线电视网在不亏本的情况下使观看转播的用户尽可能多。
输入格式
输入文件的第一行包含两个用空格隔开的整数N和M,其中2≤N≤3000,1≤M≤N-1,N为整个有线电视网的结点总数,M为用户终端的数量。
第一个转播站即树的根结点编号为1,其他的转播站编号为2到N-M,用户终端编号为N-M+1到N。
接下来的N-M行每行表示—个转播站的数据,第i+1行表示第i个转播站的数据,其格式如下:
K A1 C1 A2 C2 … Ak Ck
K表示该转播站下接K个结点(转播站或用户),每个结点对应一对整数A与C,A表示结点编号,C表示从当前转播站传输信号到结点A的费用。最后一行依次表示所有用户为观看比赛而准备支付的钱数。
输出格式
输出文件仅一行,包含一个整数,表示上述问题所要求的最大用户数。
求树上最大值并且没有后效性,我们可以考虑用dp做这题。
很容易设计出状态:dp(i,j)表示以i为根的子树上选j个点的最大价值。这里我们不局限于选叶子节点,我们可以认为不是叶子的节点价值为0。设u的子节点集合为son(u),size(u)表示u的子树大小,那么我们可以列出转移方程:
\[dp[u][i]=Max_{v{\in}son(u)}{\{}
Max_{j{\in}[0,Min(i,size(u))]}{\{}dp[u][i-j]+dp[v][j]-edge(u,v){\}}
{\}}
\]
初始化dp(i,0)=0,dp(i,1~size(i))为负无穷;若i为叶子节点,那么dp(i,1)为i愿意付的钱数。
时间复杂度为:
\[O(\sum_{u=1}^{n} (1+\sum_{v{\in}son(u)}size(v))=O(\sum_{u=1}^{n}size(u))
\]
就是N^2级别。
答案就是dp(1,i)大于等于0的数中i最大的那个。可以倒序枚举
#include<iostream>
#include<cstring>
#include<cstdio>
#define maxn 3001
using namespace std;
struct edge{
int to,dis,next;
edge(){}
edge(const int &_to,const int &_dis,const int &_next){ to=_to,dis=_dis,next=_next; }
}e[maxn];
int head[maxn],k;
int dp[maxn][maxn],size[maxn],val[maxn];
int n,m;
inline int read(){
register int x(0),f(1); register char c(getchar());
while(c<'0'||'9'<c){ if(c=='-') f=-1; c=getchar(); }
while('0'<=c&&c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar();
return x*f;
}
inline void add(const int &u,const int &v,const int &w){ e[k]=edge(v,w,head[u]),head[u]=k++; }
void dfs(int u){
size[u]=1,dp[u][0]=0;
if(u>n-m){ dp[u][1]=val[u]; return; }
for(register int i=head[u];~i;i=e[i].next){
int v=e[i].to;
dfs(v),size[u]+=size[v];
for(register int j=size[u];j>=0;j--){
for(register int k=min(j,size[v]);k>=0;k--){
dp[u][j]=max(dp[u][j],dp[u][j-k]+dp[v][k]-e[i].dis);
}
}
}
}
int main(){
memset(head,-1,sizeof head),memset(dp,~0x3f,sizeof dp);
n=read(),m=read();
for(register int i=1;i<=n-m;i++){
int k=read();
while(k--){
int v=read(),w=read();
add(i,v,w);
}
}
for(register int i=n-m+1;i<=n;i++) val[i]=read();
dfs(1);
for(register int i=m;i>=1;i--) if(dp[1][i]>=0){ printf("%d\n",i); break; }
return 0;
}