题解 P1273 【有线电视网】

LengChu

2018-07-14 10:45:21

Solution

# 树形dp(分组背包) ###### ~~蒟蒻因为看不懂题解自己写结果一次ac的故事~~ ### 说一下自己的理解 1.明确dp[i][j]含义,表示i节点,选j个用户,能得到的钱的最大值,然后对每个节点做分组背包。 2.怎么看出是分组背包?首先,背包的总容量相当于该点为根节点的子树中所有的用户数量(dp[i][j]的 j 不可能超过它连接的所有用户数)。然后,把该节点的每个儿子看成一组,每组中的元素为选一个,选两个...选n个用户。 3.转移方程 dp[i][j]=max(dp[i][j],dp[i][j-k]+dp[v][k]-这条边的花费) i,j不解释了,v表示枚举到这一组(即i的儿子),k表示枚举到这组中的元素:选k个用户。 4.最后输出dp[1][i]>=0的i的最大值,所以反向枚举。 ###### ~~代码丑~~ ------------ ### 更新一下 #### 说一下分组背包吧qwq #### 分组背包:若干组物品,每组中的物品互相冲突(就是说每组中最多只能选一件物品),求最大价值 #### ~~伪代码如下~~ ``` for(int k=1;k<=总共的组数;k++)//遍历所有的组k for(int j=v;j>=1;j--)//跟01背包类似,倒序枚举背包容量 for(int i=1;i<=组中的元素个数;i++)//遍历这组中所有的元素 ``` ~~仔细看的话和树形dp的代码是一模一样的呢~~ #### 再解释一下这道题的思路 简单来说就是把每个节点看成一个背包啦,它的容量就是以这个节点为根的子树大小,组数就是连接的儿子个数。 每组都有很多选择,选一个,两个,三个用户,把这些选择当做组中的元素就好了,容易看出每组中只能选一个元素,比如你选择了选一个用户,就不可能同时选择选两个用户。 代码: ------------ ```cpp #include<cstdio> #include<cstring> #include<algorithm> using namespace std; int n,m;//n为整个有线电视网的结点总数,m为用户终端的数量 //第一个转播站即树的根结点编号为1,其他的转播站编号为2到n-m,用户终端编号为n-m+1到n int head[3010],k; int val[3010];//用户为观看比赛而准备支付的钱数 int dp[3010][3010];//dp[i][j]表示i节点,选j个用户,能得到的钱的最大值 struct node { int to,next,w; }edge[10000010]; void adde(int u,int v,int w) { edge[++k].to=v; edge[k].next=head[u]; edge[k].w=w; head[u]=k; } int dfs(int u) { if(u>n-m)//u为用户终端 { dp[u][1]=val[u]; return 1; } int sum=0,t; for(int k=head[u];k;k=edge[k].next)//该点连接了几个节点意为有几组,遍历每一组 { int v=edge[k].to; t=dfs(v); sum+=t; //t为该组的元素个数,或是说这个儿子为根的子树大小(这里的大小指子树中用户的个数),sum为该节点最多可以选多少个用户,即背包容量 for(int j=sum;j>0;j--) { for(int i=1;i<=t;i++)//遍历组中的元素,选多少个用户相当于一个元素 { if(j-i>=0) dp[u][j]=max(dp[u][j],dp[u][j-i]+dp[v][i]-edge[k].w); } } } return sum; } int main() { memset(dp,~0x3f,sizeof(dp));//初始化一个极大负值,因为dp可能为负 scanf("%d%d",&n,&m); for(int u=1;u<=n-m;u++) { int size,v,w; scanf("%d",&size); for(int j=1;j<=size;j++) { scanf("%d%d",&v,&w); adde(u,v,w); } } for(int i=n-m+1;i<=n;i++) scanf("%d",&val[i]); for (int i=1;i<=n;i++) dp[i][0]=0;//选0个用户的花费肯定是0啦 dfs(1); for (int i=m;i>=1;i--) if (dp[1][i]>=0) { printf("%d",i); break; } return 0; } ```