[dp uestc oj] G

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;
}
原文地址:https://www.cnblogs.com/jerryRey/p/4523609.html