战略游戏:【树形DP】

😊😊 😊😊
不求点赞,只求耐心看完,指出您的疑惑和写的不好的地方,谢谢您。本人会及时更正感谢。希望看完后能帮助您理解算法的本质
😊😊 😊😊

题目描述:

Bob喜欢玩电脑游戏,特别是战略游戏。但是他经常无法找到快速玩过游戏的办法。现在他有个问题。

他要建立一个古城堡,城堡中的路形成一棵树。他要在这棵树的结点上放置最少数目的士兵,使得这些士兵能了望到所有的路。 注意,某个士兵在一个结点上时,与该结点相连的所有边将都可以被了望到。
在这里插入图片描述

请你编一程序,给定一树,帮Bob计算出他需要放置最少的士兵。

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

第1行: 1个整数N(0<N≤1500),表示树中结点的数目。

第2行…第N+1行: 每行描述每个结点信息,依次为:该结点标号i,k(后面有k条边与结点i相连),接下来k个数,分别是每条边的另一个结点标号r1,r2,…,rk。

对于一个N个结点的树,结点标号在0到n-1之间,在输入文件中每条边只出现一次。

输出:
输出仅包含一个数,为所求的最少的士兵数目。

输入样例:

样例1
4
0 1 1
1 2 2 3
2 0
3 0
样例2
5
3 3 1 4 2
1 1 0
2 0
0 0
4 0

输出样例:

样例1
1
样例2
2

小白到进阶各种解法:

一、暴搜:待更新😊

在这里插入图片描述

代码:


在这里插入图片描述

二、记忆化搜索:待更新😊

在这里插入图片描述

代码:


在这里插入图片描述

三、本题考察算法:树形DP😊

思路:

  1. 首先思考答案的来源,想想答案的子集在哪里?
    在这里插入图片描述
    请注意哦,题目中说了不是父节点放了,子节点就不能放,本题和 上司的舞会 那道题很类似,只是状态计算的公式不同,对比这两道题也可以看出来:为什么状态状态计算的公式不同!

因为上司的舞会中,每个节点都有一个开心值,即每个节点都有一个权值。但是父子节点的权值会相互影响,所以要谨慎考虑选还是不选,选了父节点后,就不会选子节点。而对于本题而言,没有权值,只有数量。所以父子节点有时候同时选择,更优!

有同学可能会直接就按照上司的舞会那道题目进行求解。实则不然,因为本题会出现这样的边界情况:很明显存在两个父子节点直接相连,但是同时选择两个节点,才是最优解!
在这里插入图片描述

  1. 由于答案很多,最坏情况下可以有1500个节点,每个节点都有选或者不选两种情况,所以说是 21500级别的,必然会超时。既然是因为子集数量的原因引发的,所以将子集进行划分集合。
  2. 观察答案子集可以发现,我们可以以子树为单位来进行观察,比如:子树为:
    在这里插入图片描述
    对于根节点我们是选还是不选呢?取决于它的子节点的情况,有两种情况:
    1.子树中的根节点只有一层子节点:如下所示,此时应该选根节点,更加划得来!
    在这里插入图片描述
    2.子树中的根节点有多层子孙节点:请问这时候选根节点划得来吗?显然划不来,应该选根节点下面的那一层节点。图中绿色部分。
    在这里插入图片描述
    但是我们怎么知道根节点的子节点下面有没有子节点呢?怎么知道根节点有没有孙子呢?所以我们需要递归处理子树的根节点的子树!递归到叶子节点的时候开始回溯!从而确定根节点是否选还是不选,而递归到叶子节点的时候,叶子节点是单独一棵子树,同样也有选和不选两种状态。
  3. 所以经过观察可得,每个节点都有选和不选两种状态,所以设状态表示为 f [ u ] [ 1 / 0 ] : f[u][1/0]: f[u][1/0]即表示以 u u u 为根节点的子树中,在 u u u上放置哨兵和不放置哨兵的方案数量中,士兵数量最少的方案!
  4. 状态计算:父节点选了,子节点可选可不选,取决于哪种方案更优,所以说才要枚举选和不选的两个分支,然后取最小值。而父节点不选的话,由于父节点和子节点之间有边的关系,所以父节点不选,那么为了使得之间的边必须被观察到,所以子节点只有选!
    公式为:
    在 u 上放置哨兵: 在u上放置哨兵: u上放置哨兵:
    f u , 1 = ∑ i = c h i l d r e n m i n ( f i , 0 , f i , 1 ) ,   i ∈ { v e r ∣ v e r 是  i  的子节点 } ; \qquad f_{u,1} = \sum\limits_{i=children}min(f_{i,0},f{i,1}),\ i\in\{ver|ver是\ i\ 的子节点\}; fu,1=i=childrenmin(fi,0,fi,1), i{verver i 的子节点};
    在 u 上不放置哨兵: 在u上不放置哨兵: u上不放置哨兵:
    f u , 0 = ∑ i = c h i l d r e n f i , 1 ; \qquad f_{u,0}=\sum\limits_{i=children}f_{i,1}; fu,0=i=childrenfi,1

代码:

#include<iostream>
#include<cstring>

using namespace std;

const int N = 1510;
int h[N], e[N<<1], ne[N<<1], idx;
int n;
int f[N][2];
bool st[N];

void add (int a, int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
}

void dfs(int u)
{
    f[u][1] = 1;
    f[u][0] = 0;
    for (int i=h[u]; ~i; i = ne[i])
    {
        int j = e[i];
        
        dfs(j);
        f[u][1] += min(f[j][1], f[j][0]);
        f[u][0] += f[j][1];
    }
    return ;
}

int main()
{
    while (~scanf("%d", &n))
    {
        memset (h, -1, sizeof (h));
        idx = 0;
        memset (st, false, sizeof(st));
        for (int i=0; i < n; i ++)
        {
            int id, cnt;
            scanf ("%d:(%d)\n", &id, &cnt);
            while (cnt --)
            {
                int ch;
                scanf ("%d", &ch);
                add (id, ch);
                st[ch] = true;
            }
        }
        int root = 0;
        while (st[root]) root ++;
        dfs(root);
        cout << min(f[root][1], f[root][0]) << endl;;
    }
    
    return 0;
}

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值