/*
这道题还不错,自己想出了思路过得也比较快,也得出了一个小经验,以后写这种题先把关键部分伪代码写出来这样会快很多而且
不那么容易出错,省去很多的调试时间
这道题就是转化为一道树形背包问题。首先把需要付的钱转为负数,对每个叶子结点增加一个子节点表示赚的钱,为正数.
然后记录下当前结点的所有可能的用户数目所花费的钱.所以问题就转化为一道简单的树形dp问题。最后找出盈利为非负数的最大
用户数即可.
有一点要注意dp数组初始化为一个很大的负数,刚开始因为这个问题wa了一遍
用到了num数组存放每个结点的叶子结点的数目,起到很好的优化作用。
*/
#include<cstdio> #include<cstring> #include<cstdlib> #include<vector> #include<iostream> using namespace std; #define maxn 6005 int dp[maxn][maxn],num[maxn],val[maxn]; vector<int>sons[maxn]; //num对于优化起了很大的作用,来记录当前结点子树中的叶子结点数目 void dfs(int rt){ dp[rt][0]=0; for(int u=0;u<sons[rt].size();u++){ int s=sons[rt][u]; dfs(s); num[rt]+=num[s]; for(int i=num[rt]-num[s]+1;i<=num[rt];i++) dp[rt][i]=-1000000; //要初始化为一个很大的负数 for(int i=num[rt];i>=1;i--){ for(int j=1;j<=num[s]&&j<=i;j++){ dp[rt][i]=max(dp[rt][i],dp[s][j]+dp[rt][i-j]+val[s]); } } } } void init(){ for(int i=0;i<maxn;i++) sons[i].clear(); memset(val,0,sizeof(val)); memset(num,0,sizeof(num)); } void print(){ for(int i=num[1];i>=0;i--) if(dp[1][i]>=0) {printf("%d\n",i);break;} } int main(){ int i,j,n,m,k,a,b; while(~scanf("%d%d",&n,&m)){ init(); for(i=1;i<=n-m;i++){ scanf("%d",&k); for(j=1;j<=k;j++){ scanf("%d%d",&a,&b); sons[i].push_back(a); val[a]=-b; } } for(i=n+1;i<=n+m;i++){ //给每一个叶子结点加一个结点,用它来表示赚的钱,而其它点都表示需要支付的钱 scanf("%d",&val[i]); num[i]=1; sons[i-m].push_back(i); } dfs(1); print(); } return 0; }
时间: 2024-10-24 00:03:14