HDU 1561 - The more, The Better
http://blog.csdn.net/woshi250hua/article/details/7637847
Problem Description
ACboy很喜欢玩一种战略游戏,在一个地图上,有N座城堡,每座城堡都有一定的宝物,在每次游戏中ACboy允许攻克M个城堡并获得里面的宝物。但由于地理位置原因,有些城堡不能直接攻克,要攻克这些城堡必须先攻克其他某一个特定的城堡。你能帮ACboy算出要获得尽量多的宝物应该攻克哪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
对于每个测试实例,输出一个整数,代表ACboy攻克M个城堡所获得的最多宝物的数量
Sample Input
3 2 0 1 0 2 0 3 7 4 2 2 0 1 0 4 2 1 7 1 7 6 2 2 0 0
Sample Output
5 13
解题思路:和Poj1155、1947一样,都是在树形结构上进行分组背包处理。本题的依赖关系可以理解成树边,到达子节点必须先经过父节点,本质是一样的,这样可以建立起一个森林,如果为每棵树的添加一个公共父节点0那就成了一棵树了,这是个实用性很强的小技巧。建立起一棵树就开始如何进行dp,如何从子节点获取信息。
一个子节点就可以返回m个状态,每个状态表示容量为j(j<=m)时选最多的宝物,而一个子节点中只可以选择一个状态进行转移,每个节点有若干个子节点,问题就转换为分组背包,几个子节点就是几个分组背包,体积是选几个地点,价值是宝物价值。
状态转移方程: dp[v][1] = Money[v]; (v为叶子节点)
dp[v][j] = max(dp[v][j],dp[v][j-i] + dp[k][i] );(v为非叶子节点,j表示用户个数,i为容量,k为v的子节点,)
算法复杂度O(sum(num[i],num[s])) (num[i]为某个节点的叶子子孙个数,num[s]为i的子节点的叶子子孙个数)
dp[v][j] = max(dp[v][j],dp[v][j-i] + dp[k][i] );(v为非叶子节点,j表示用户个数,i为容量,k为v的子节点,)
算法复杂度O(sum(num[i],num[s])) (num[i]为某个节点的叶子子孙个数,num[s]为i的子节点的叶子子孙个数)
本题加了个剪枝,每次转移时不必都从最大容量m开始,而可以把当前可能转移的最大容量算出来进行转移,最大容量就是子节点个数,速度快了10几倍。
以树来存储各城堡之间的依赖关系。
首先说状态表示,dp[i][j]表示在以i为根节点的子树上攻破j个城堡可达到的最大收益。
为方便表示,以0号节点为总的根节点。
http://blog.csdn.net/harrypoirot/article/details/25426315- vector<int> map[205];
- int val[205],dp[205][205];
- int n,m;
- //dp[i][j]:以第i个节点为起始,攻击j次的最大获益
- inline int bet(int x,int y)
- {
- if(x>y) return x;
- return y;
- }
- void dfs(int bg,int c)
- {
- int size=map[bg].size();
- dp[bg][1]=val[bg];//只攻击该节点
- for(int i=0;i<size;++i)//对于bg的所有子节点做01背包
- {
- if(c>1) dfs(map[bg][i],c-1);
- //若未到最后,则继续向下深搜,先算好将来所需的数据
- for(int j=c;j>1;--j)//当背包容量为c时,j=1时为只选bg
- {
- for(int k=0;k<=j-1;++k)
- {
- //攻击了第i个子节点下的k个节点,那么还剩下j-k次机会攻击其余的
- dp[bg][j]=bet(dp[bg][j],dp[bg][j-k]+dp[map[bg][i]][k]);
- }
- }
- }
- }
- int main()
- {
- while(cin>>n>>m)
- {
- if(n==0&&m==0) break;
- memset(val,0,sizeof(val));
- memset(dp,0,sizeof(dp));
- for(int i=0;i<=n;++i) map[i].clear();
- int a,b;
- for(int i=1;i<=n;++i)
- {
- scanf("%d%d",&a,&b);
- map[a].push_back(i);
- val[i]=b;
- }
- dfs(0,m+1);//从‘0’开始
- cout<<dp[0][m+1]<<endl;
- }
- }
- /*
- dp[i][j]代表以i为根节点,一共选取j个节点所取得的最大价值,i这个节点一定要选
- dp_temp[i][j]代表以i为根节点,一共选取j个节点所取得的最大价值,i这个节点一定不选
- 那么 dp[i][j+1]=dp_temp[i][j]+val[i];
- dp_temp[i][j+k]=max(dp_temp[i][j+k],dp_temp[i][j]+dp[son[i]][k]);
- 解释一下 dp_temp[i][j]+dp[son[i]][k],其中 son[i]代表i这个节点的孩子节点,
- 假设son[i]这个节点中存在选取K个节点这个状态,那么这个孩子节点就
- 可以向dp_temp[i][j+k]转移,因为转移的时候都是从孩子转移过来的,
- 那么要是选择了son[i]这个节点,这个节点本身是一定要选择的,
- 所以转移方程不是 + dp_temp[son[i]][k],而是 +dp[son[i]][k]
- */
- vector<int> E[N];
- int dp_temp[N][N],dp[N][N];
- int val[N],n,m;
- bool h[N];
- void init(int n){
- val[0]=0;
- for(int i=0;i<=n;i++)
- E[i].clear();
- memset(h,0,sizeof(h));
- memset(dp,-1,sizeof(dp));
- memset(dp_temp,-1,sizeof(dp_temp));
- }
- void dfs(int u){
- if(h[u]) return;
- h[u]=1;
- dp_temp[u][0]=0;
- for(int i=0;i<E[u].size();i++){
- int v=E[u][i];
- if(!h[v])
- dfs(v); //叶子节点未访问过则继续访问
- for(int j=m;j>=0;j--)
- for(int k=1;k+j<=m;k++)
- if(dp_temp[u][j]!=-1&&dp[v][k]!=-1){
- if(dp_temp[u][j+k]<dp_temp[u][j]+dp[v][k])
- dp_temp[u][j+k]=dp_temp[u][j]+dp[v][k];
- }
- }
- for(int i=0;i<=m;i++)
- if(dp_temp[u][i]!=-1)
- dp[u][i+1]=dp_temp[u][i]+val[u];
- }
- int main(void){
- while(scanf("%d%d",&n,&m),n||m){
- init(n);
- for(int i=1;i<=n;i++){
- int u;
- scanf("%d%d",&u,&val[i]);
- E[u].push_back(i);
- }
- dfs(0);
- printf("%d\n",dp[0][m+1]);
- }
- }