Hduoj1011【树状DP】

#include<stdio.h>
#include<string.h>
typedef struct room
{
	int bug, po;
} room;
room R[110];//记录原始数据 
int vis[110], next[110], n, m, tree[110][110]; 
int  dp[110][110];//dp[i][j]代表以第i个节点为根节点的树在拥有j个士兵的情况下所能获得首脑的最大概率 
int Max(int x, int y)
{
	return x>y?x:y;
}
void dfs(int a)
{
	int i, j, k, cost;
	vis[a] = 1;
	cost = (R[a].bug + 19) / 20;//记录该节点所需要的士兵个数 
	for(i = cost; i <= m; ++i)//初始化该节点能获得的概率 
	dp[a][i] = R[a].po;
	for(i = 0; i <= next[a]; ++ i)
	{
		int b = tree[a][i];//子节点的编号
		if(!vis[b])
		{
			dfs(b);//求子节点能获得的最大概率 
			for(j = m; j >= cost; --j)//回溯求父节点的最大概率 
			for(k = 1; j+k <= m; ++k)
			{
				if(dp[b][k])//如果说子节点有概率存在,更新父节点的值 
				dp[a][j+k] = Max(dp[a][j+k], dp[a][j] + dp[b][k]);
			}
		}
	}
	return ;
}
int main()
{
	int i, j, k;
	while(scanf("%d%d", &n, &m) != EOF && ( n!= -1 || m != -1))
	{
		for(i = 1; i <= n; ++i)//编号从1开始 
		scanf("%d%d", &R[i].bug, &R[i].po);
		memset(next, -1, sizeof(next));
		for(i = 0; i < n-1; ++i)
		{
			int a, b;
			scanf("%d%d", &a, &b);
			tree[a][++next[a]] = b;//next数组保存节点的子节点的个数 
			tree[b][++next[b]] = a;//tree保存该节点的子节点的编号 
		}
		if(!m )
		{
			printf("0\n");
		} 
		else
		{
			memset(dp, 0, sizeof(dp));
			memset(vis, 0, sizeof(vis));
			dfs(1);
			printf("%d\n", dp[1][m]);
		}
	}
	return 0;
}

题意:有一个虫穴,虫穴中有血多个房间,每个房间有一定数量的虫子,并且有一定的概率会有首脑存在,现在需要多个首脑来做实验,要求你去抓捕首脑。给定你m个士兵,每个士兵可以对付20个虫子,对于每个房间,想要进入该房间必须消灭该房间的上一个房间的虫子,每消灭一个房间的虫子就会损失相应数量的士兵(不足20个士兵也需要一个士兵来消灭)每消灭一个房间的虫子就会获得该房间对应的概率,现在给出该虫穴的数据,房间从1~n编号,并且规定起点为1好放假,求m个士兵所能获得的最大概率。

思路:首先该虫穴的图形可以明显看得出是一个树形图,对于从上往下每一个节点都可以选择要或是不要,这有点类似于背包,若是要则需要扣除一定的士兵,并且能够获得一定的概率,所以可以用dp来做,一个dp【i】【j】表示以编号i为根节点的树在拥有j个士兵的情况下所能获得的最大概率,从树的根节点从上往下开始遍历,再从叶子几点开始回溯,因为对于叶子节点来说,在有1~m个士兵的时候所能获得的概率只有一种情况(士兵数大于该节点所需要的士兵,获得该点的概率,否则为0),接下来回溯父节点的dp状况,对于父节点来说,本身在士兵个数大于消耗的情况下能获得该点的概率,但是有可能该点的概率值在某个士兵的个数下可以获得该点的概率值并加上子节点的概率值,所以需要更新该点的概率值,如此一直回溯到该数的根节点即dp【1】【m】,即为答案。

难点:该题的难点在于找到dp方程, 即背包的思路,穷举一个节点所能获得概率值的所有情况,接着就是建树进行遍历了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值