树形DP
同选课很像,即树上的背包问题。
f[i][j]
f
[
i
]
[
j
]
表示以
i
i
为根的树中选个节点所能达到的最大权值。
转移方程很好想:
f[x][j]=max(f[x][j],f[ed[i].to][p]+f[x][j-p]-ed[i].dis);
因为这道题有边权,因此更新时要减掉。
但是这道题数据范围比较大,在枚举
j
j
与时不能直接从n开始枚举。
j
j
从其目前遍历到的所有节点个数开始枚举,从当前的节点个数开始枚举,可以并不会证明这样做复杂度为
O(n2)
O
(
n
2
)
。
代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
#define MAXN 3000
using namespace std;
struct edge{
int next,to,dis;
};
int n,m,k;
int h[MAXN+5],f[MAXN+5][MAXN+5],w[MAXN+5];
edge ed[MAXN*2+5];
int dp(int x){
if (x>n-m){//只有最后的几个点才有点权
f[x][1]=w[x];
return 1;//当前遍历到1个
}
int sum=0;//记录目前总共遍历了几个
for (int i=h[x];i;i=ed[i].next){
int t=dp(ed[i].to); sum+=t;
for (int j=sum;j>=1;j--)
for (int p=t;p>=1;p--)
f[x][j]=max(f[x][j],f[ed[i].to][p]+f[x][j-p]-ed[i].dis);
}
return sum;
}
void addedge(int x,int y,int z){
ed[++k].next=h[x]; ed[k].to=y; ed[k].dis=z; h[x]=k;
}
int main(){
scanf("%d%d",&n,&m);
for (int i=1;i<=n-m;i++){
int p,v,t;
scanf("%d",&p);
for (int j=1;j<=p;j++){
scanf("%d%d",&t,&v);
addedge(i,t,v);//这里建单向边即可
}
}
for (int i=n-m+1;i<=n;i++){
int v;
scanf("%d",&v);
w[i]=v;
}
int ans=0;
memset(f,-63,sizeof(f));
for (int i=1;i<=n;i++)
f[i][0]=0;
dp(1);
for (int i=n;i>=1;i--)
if (f[1][i]>=0){
printf("%d\n",i);
break;
}
return 0;
}