小胖守皇宫(VIJOS P1144 )题解

题目描述

huyichen世子事件后,xuzhenyi成了皇上特聘的御前一品侍卫。 皇宫以午门为起点,直到后宫嫔妃们的寝宫,呈一棵树的形状;某些宫殿间可以互相望见。大内保卫森严,三步一岗,五步一哨,每个宫殿都要有人全天候看守,在不同的宫殿安排看守所需的费用不同。 可是xuzhenyi手上的经费不足,无论如何也没法在每个宫殿都安置留守侍卫。 帮助xuzhenyi布置侍卫,在看守全部宫殿的前提下,使得花费的经费最少。

输入格式

输入文件中数据表示一棵树,描述如下:

第1行 nn,表示树中结点的数目。

第2行至第n+1n+1行,每行描述每个宫殿结点信息,依次为:该宫殿结点标号i0<ini(0<i≤n),在该宫殿安置侍卫所需的经费kk,该边的儿子数mm,接下来mm个数,分别是这个节点的mm个儿子的标号r1r2...rmr1,r2,...,rm。

对于一个n0<n1500n(0<n≤1500)个结点的树,结点标号在1到nn之间,且标号不重复。

输出格式

输出文件仅包含一个数,为所求的最少的经费。


由于宫殿节点图是树的形状,所以很明显这是树形DP。

首先二维状态,dp[i][j],j∈{1,2,3}

dp[i][1]表示这个点被自己守卫。

dp[i][2]表示这个点被父亲守卫。

dp[i][3]表示这个点被儿子守卫。

根据属性DP惯用套路,首先大法师(DFS)搜索到叶节点,然后向上更新。

如果这个点被自己守卫,那么他的儿子可能有三种状态,既可能是自己守卫,又可能被父亲守卫,还有可能被它的儿子守卫。

用s来代表x的儿子,所以:

dp[x][1] += min(dp[s][2],min(dp[s][3],dp[s][1]));

如果这个点被父亲守卫,那么他的儿子只可能被自己守卫,或者被它的儿子守卫。

dp[x][2] += min(dp[s][1],dp[s][3]);

那么最困难的是这个点被自己的儿子守卫,那么他的所有儿子同样是两种状态,被自己守卫或者被它的儿子守卫。且一定存在一个儿子被自己守卫。

如果更新了一圈后,发现所有的儿子自己守卫的代价都要大于它们的儿子守卫它们的价值(即x的儿子s守卫的价值大于s的儿子守卫的价值),我们需要加上一个s自己守卫和s的儿子守卫的差量,并且保证这个差量最小。

注意把所有点自己守卫自己的情况先赋上值。

代码:

#include<cstdio>
#include<algorithm>
#define N 1555
using namespace std;
int money[N];
int son[N][N];
int dp[N][4];
void dfs(int x)
{
    if(!son[x][0])
    {
        dp[x][1] = money[x];
        dp[x][3] = money[x];
        dp[x][2] = 0;
        return ;
    }else
    {
        for(int i = 1;i<=son[x][0];i++)
        {
            dfs(son[x][i]);
        }
        for(int i = 1;i<=son[x][0];i++)
        {
            int s = son[x][i];
            dp[x][1] += min(dp[s][2],min(dp[s][3],dp[s][1]));//自己 
            dp[x][2] += min(dp[s][1],dp[s][3]);//父亲 
        }
        bool flag = 0;
        int bu = 2147438647;
        for(int i = 1;i<=son[x][0];i++)
        {
            int s = son[x][i];
            dp[x][3] += min(dp[s][1],dp[s][3]);
            if(dp[s][3]>=dp[s][1])
            {
                flag = 1;
            }
            bu = min(bu,dp[s][1]-dp[s][3]);
        }
        if(flag==0)
        {
            dp[x][3]+=bu;
        }
    }
}
int main()
{
    int n;
    scanf("%d",&n);
    int root;
    for(int i = 1;i<=n;i++)
    {
        int num;
        scanf("%d",&num);
        if(i==1)
        {
            root = num;
        }
        scanf("%d",&money[num]);
        dp[num][1] = money[num];
        scanf("%d",&son[num][0]);
        for(int j = 1;j<=son[num][0];j++)
        {
            scanf("%d",&son[num][j]);
        }
    }
    dfs(root);
    printf("%d",min(dp[root][1],dp[root][3]));
}

 

转载于:https://www.cnblogs.com/lizitong/p/10024852.html

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值