Vijo114 小胖守皇宫

F[X][0]表示结点X的父亲放了守卫的最小花费,F[X][1]表示结点X自身放了守卫的最小花费,F[X][2]表示结点X和父亲都不放守卫,X的其中一个儿子放了守卫的最小花费。

这样,父亲放了守卫,X可以选择放守卫(F[X][1]),或者可以是儿子放自身不放守卫(F[son][1],F[son][2])。

自身放守卫,则儿子的状态都是父亲放了守卫(F[son][0])。

其中一个儿子放了守卫,则枚举哪个儿子是最优值。(F[son][1],F[son][0]),这样最终可以推出转移方程。

#include<cstdio>
#include<algorithm>
#include<iostream>
#define mem(a,b) memset(a,b,sizeof(a))
#define maxn 1502
#define MAX 1000000007
using namespace std;
typedef long long ll;
struct st{
	int num,c;//num儿子数,c代价
	int s[maxn];
}a[maxn];
int u[maxn];
int f[maxn][4];
void dp(int now)
{
	if(u[now])
	{
		return ;
	}
	if(!a[now].num)
	{
		f[now][1]=f[now][2]=a[now].c;
		f[now][0]=0;
	}
	for(int i=1;i<=a[now].num;i++)
	{
		dp(a[now].s[i]);//枚举子节点,搜索
	}
	f[now][0]=0;
	for(int j=1;j<=a[now].num;j++)
	{
		f[now][0]+=min(f[a[now].s[j]][1],f[a[now].s[j]][2]);
	}
	f[now][2]=MAX;
	for(int j=1;j<=a[now].num;j++)
	{
		f[now][2]=min(f[now][2],f[now][0]-min(f[a[now].s[j]][1],f[a[now].s[j]][2])+f[a[now].s[j]][1]);
		
	}
	f[now][1]=a[now].c;
	for(int j=1;j<=a[now].num;j++)
	{
		f[now][1]+=f[a[now].s[j]][0];//加上所有子节点的代价
	}
	f[now][0]=min(f[now][0],f[now][1]);//父节点看守或自己看守
}
int main()
{
	int n;
    while(~scanf("%d",&n))
	{
		mem(f,MAX);
		for(int i=1;i<=n;i++)
		{
			int x;
 			scanf("%d",&x);
 			scanf("%d%d",&a[x].c,&a[x].num);
			for(int j=1;j<=a[x].num;j++)
			{
				scanf("%d",&a[x].s[j]);
				u[a[x].s[j]]=1;
			}
		}
		int m,ans;
		for(m=1;m<=n;m++)if(!u[m])break;
		mem(u,0);
		dp(m);
		ans=min(f[m][1],f[m][2]);//自己放或儿子放
		printf("%d\n",ans);
	}
	return 0;
}

2020-03-17 一更
这道题就是互为父节点子节点中有一个有人看守即可。

所以一个点自己看守的代价就是所有子节点状态为父亲看守的加和。

f[now][1]+=f[a[now].s[j]],[0];

一个点父亲看守的代价就是min(这个父亲看守,这个点看守)。

f[now][0]=min(f[now][1],f[now][0]);

一个点儿子看守的代价就是min(这个点父亲看守的代价减去这个子节点自己看守或子节点的儿子看守之间的最小代价再加上这个子节点自己看守的代价,本身)。

好像很难理解,看下代码(注意此时的父节点代价不是最终的)

f[now][2]=min(f[now][0]-min(f[a[now].s[j]][1],f[a[now].s[j]][2])+f[a[now].s[j]][1],f[now][2]);

注意是先遍历儿子在遍历根,最后输出根,从下往上遍历树,根的下标是m。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值