POJ 1155 TELE - 树形DP

一道类似于背包的题,一眼便能看出是树形DP

树形DP大概的思想大概就是虚树思想:先选定一个子节点,然后以此节点作为左儿子和右边一个儿子合并共同记为左儿子,其他右儿子一个个向左合并。

dp[i][j]意为编号为i的节点的所有子树共同选定j个用户所获得的利润,利润为负则是亏本。

一开始肯定都要赋-INF 的初值

DP方程很好写啊:

for(int j=m;j>=0;j--)//枚举根节点能取得用户数
for(int k=0;k<=size[e[i].to];k++)//枚举子节点能取得的用户数
dp[x][j]=max(dp[x][j],dp[e[i].to][k]+dp[x][j-k]-e[i].val);//原本的子节点的状态和新子节点的合并

然而写完这个以后完全过不了样例。。。-INF互加是个什么鬼。。。

后来发现别人代码都有判断是否可以转移的步骤,加一个if,如果需要转移的两个状态有一个为-INF(即无状态转移到此状态)则必定不能选


然后发现还是过不了。。。老爆0是个什么操作。。。

然后加一个初值就好了


一般这种题的套路就是

1)初值

2)for枚举子树,递归+合并

3)给不合理的越界情况赋-1


#include<iostream>
#include<cstdlib>
#include<cstdio> 
#include<cstring>
#include<algorithm>

using namespace std;

const int maxn=3005;
const int INF=0x3f3f3f3f;

struct edge
{
	int to,next,val;
}e[maxn];

int n,m,cnt;
int size[maxn],dp[maxn][maxn],head[maxn],s[maxn];

void insert(int a,int b,int val)
{
	e[++cnt].to=b;e[cnt].val=val;e[cnt].next=head[a];head[a]=cnt;
}

void dfs(int x)
{
	size[x]=1;
	dp[x][0]=0;
	for(int i=head[x];i;i=e[i].next)
	{
		dfs(e[i].to);
		size[x]+=size[e[i].to];
		for(int j=m;j>=0;j--)
			for(int k=0;k<=size[e[i].to];k++)
				if(dp[e[i].to][k]!=-INF&&dp[x][j-k]!=-INF)
					dp[x][j]=max(dp[x][j],dp[e[i].to][k]+dp[x][j-k]-e[i].val);
	}
	if(x>=n-m+1)
		dp[x][1]=s[x-n+m];
	for(int i=m;i>=0;i--)if(i>size[x])dp[x][i]=-INF;
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n-m;i++)
	{
		int a,b,c;
		scanf("%d",&c);
		while(c--)
		{
			scanf("%d%d",&a,&b);
			insert(i,a,b);
		}
	}
	for(int i=1;i<=m;i++)
		scanf("%d",s+i);
	memset(dp,-INF,sizeof dp);
	dfs(1);
	for(int i=m;i>=0;i--)
		if(dp[1][i]>=0||!i)
	{
		printf("%d",i);
		return 0;
	}
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值