一道类似于背包的题,一眼便能看出是树形DP
树形DP大概的思想大概就是虚树思想:先选定一个子节点,然后以此节点作为左儿子和右边一个儿子合并共同记为左儿子,其他右儿子一个个向左合并。
dp[i][j]意为编号为i的节点的所有子树共同选定j个用户所获得的利润,利润为负则是亏本。
一开始肯定都要赋-INF 的初值
DP方程很好写啊:
for(int j=m;j>=0;j--)//枚举根节点能取得用户数
for(int k=0;k<=size[e[i].to];k++)//枚举子节点能取得的用户数
dp[x][j]=max(dp[x][j],dp[e[i].to][k]+dp[x][j-k]-e[i].val);//原本的子节点的状态和新子节点的合并
然而写完这个以后完全过不了样例。。。-INF互加是个什么鬼。。。
后来发现别人代码都有判断是否可以转移的步骤,加一个if,如果需要转移的两个状态有一个为-INF(即无状态转移到此状态)则必定不能选
然后发现还是过不了。。。老爆0是个什么操作。。。
然后加一个初值就好了
一般这种题的套路就是
1)初值
2)for枚举子树,递归+合并
3)给不合理的越界情况赋-1
#include<iostream>
#include<cstdlib>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=3005;
const int INF=0x3f3f3f3f;
struct edge
{
int to,next,val;
}e[maxn];
int n,m,cnt;
int size[maxn],dp[maxn][maxn],head[maxn],s[maxn];
void insert(int a,int b,int val)
{
e[++cnt].to=b;e[cnt].val=val;e[cnt].next=head[a];head[a]=cnt;
}
void dfs(int x)
{
size[x]=1;
dp[x][0]=0;
for(int i=head[x];i;i=e[i].next)
{
dfs(e[i].to);
size[x]+=size[e[i].to];
for(int j=m;j>=0;j--)
for(int k=0;k<=size[e[i].to];k++)
if(dp[e[i].to][k]!=-INF&&dp[x][j-k]!=-INF)
dp[x][j]=max(dp[x][j],dp[e[i].to][k]+dp[x][j-k]-e[i].val);
}
if(x>=n-m+1)
dp[x][1]=s[x-n+m];
for(int i=m;i>=0;i--)if(i>size[x])dp[x][i]=-INF;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n-m;i++)
{
int a,b,c;
scanf("%d",&c);
while(c--)
{
scanf("%d%d",&a,&b);
insert(i,a,b);
}
}
for(int i=1;i<=m;i++)
scanf("%d",s+i);
memset(dp,-INF,sizeof dp);
dfs(1);
for(int i=m;i>=0;i--)
if(dp[1][i]>=0||!i)
{
printf("%d",i);
return 0;
}
}