G - 邱老师玩游戏
Time Limit: 3000/1000MS (Java/Others) Memory Limit: 65535/65535KB (Java/Others)
邱老师最近在玩一种战略游戏,在一个地图上,有N座城堡,每座城堡都有一定的宝物,在每次游戏中邱老师允许攻克M个城堡并获得里面的宝物。
但由于地理位置原因,有些城堡不能直接攻克,要攻克这些城堡必须先攻克其他某一个特定的城堡。你能帮邱老师算出要获得尽量多的宝物应该攻克哪M个城堡吗?
Input
每个测试实例首先包括2个整数,N,M.(1 <= M <= N <= 200);
在接下来的N行里,每行包括2个整数,a,b.
在第 i 行,a 代表要攻克第 i 个城堡必须先攻克第 a 个城堡,如果 a = 0 则代表可以直接攻克第 i 个城堡。b 代表第 i 个城堡的宝物数量, b >= 0。
当N = 0, M = 0输入结束。
Output
对于每个测试实例,输出一个整数,代表邱老师攻克M个城堡所获得的最多宝物的数量。
树形dp,学了两种建树的方式,保存边集和保存子结点。这题枚举的顺序很重要,当打叶子结点时,dp[leaf][m]=val[n];打非叶子结点时,子节点v分配k个点数,其余子节点分配m-k-1个点数,怎么实现这点呢?如果dp[V][k]等于0,就等效于没有这个结点,因此可以一个一个的算出dp[Vi]。dp[Ui]表U为根结点子节点为1~i结点的树,算出dp[V(i+1)]相当于增加了一个子节点,dp[Ui]就保存新的这棵树的其余子节点的值。因此在代码中j必须逆序枚举,原理类似滚动数组。
#include<cstdio> #include<vector> #include<algorithm> #include<memory.h> const int MX=201; int dp[MX][MX]; bool vis[MX]; int val[MX]; std::vector<int> son[MX]; int m; void dfs(int u)//打u前剩下m点 结果保存在dp[u][1~m] { if(vis[u])return; dp[u][1]=val[u]; for(int i=0;i<son[u].size();i++) { int v=son[u][i]; if(!vis[v]) dfs(v); for(int j=m;j>1;j--)//保证每个结点最多被选一次; for(int k=1;k<j;k++) dp[u][j]=std::max(dp[u][j],dp[u][j-k]+dp[v][k]); } } int main() { //freopen("Gin.txt","r",stdin); int n; int a; int i; while(~scanf("%d%d",&n,&m)&&n)//EOF=-1; ~(-1)=0 { memset(vis,0,sizeof(vis)); memset(dp,0,sizeof(dp)); for(i=0;i<=n;i++) son[i].clear(); for(i=1;i<=n;i++) { scanf("%d%d",&a,val+i); son[a].push_back(i); } m++; dfs(0); printf("%d ",dp[0][m]); } //while(1); return 0; }