P1273 有线电视网

斯德哥尔摩

2018-10-24 20:31:07

Personal

[P1273 有线电视网](https://www.luogu.org/problemnew/show/P1273) 这是一个树上背包。 设$dp[i][j]$表示在$i$节点,选$j$个用户,能得到的钱的最大值,然后对每个节点做分组背包。 什么?你不知道分组背包??? 分组背包:若干组物品,每组中的物品互相冲突,即:每组中最多只能选一件物品,求最大价值。 怎么看出是分组背包? 首先,背包的总容量相当于该点为根节点的子树中所有的用户数量,即:$dp[i][j]$的$j$不可能超过它连接的所有用户数。 然后,把该节点的每个子树看成一组,每组中的元素为选$1,2,3,...,k$个用户。 于是转移方程破壳而出: $$dp[i][j]=\max\{dp[i][j-k]+dp[v][k]-cost[i][j]\quad|\quad\text{v为i的儿子}\}$$ $k$表示枚举到这组中的元素,即:选$k$个用户。 $cost[i][j]$为$i,j$之间的边的耗费。 最后输出$dp[1][i]>=0$的i的最大值,反向枚举就好。 附代码: ```cpp #include<iostream> #include<algorithm> #include<cstdio> #include<cstring> #define MAXN 3010 using namespace std; int n,m,c=1; int val[MAXN],head[MAXN],dp[MAXN][MAXN]; struct Edge{ int next,to,w; }a[MAXN*MAXN]; inline int read(){ int date=0,w=1;char c=0; while(c<'0'||c>'9'){if(c=='-')w=-1;c=getchar();} while(c>='0'&&c<='9'){date=date*10+c-'0';c=getchar();} return date*w; } inline void add_edge(int u,int v,int w){ a[c].to=v;a[c].w=w;a[c].next=head[u];head[u]=c++; } int dfs(int rt){ dp[rt][0]=0; if(rt>n-m){ dp[rt][1]=val[rt]; return 1; } int sum=0,s; for(int i=head[rt];i;i=a[i].next){ int will=a[i].to; s=dfs(will); sum+=s; for(int j=sum;j>0;j--) for(int k=1;k<=s;k++) if(j-k>=0) dp[rt][j]=max(dp[rt][j],dp[rt][j-k]+dp[will][k]-a[i].w); } return sum; } void work(){ for(int i=m;i>=1;i--) if(dp[1][i]>=0){ printf("%d\n",i); break; } } void init(){ int x,y,k; n=read();m=read(); memset(dp,-127,sizeof(dp)); for(int i=1;i<=n-m;i++){ k=read(); while(k--){ x=read();y=read(); add_edge(i,x,y); } } for(int i=n-m+1;i<=n;i++)val[i]=read(); dfs(1); } int main(){ init(); work(); return 0; } ```