P2279 消防局的设立 (树形DP or 贪心)

(点击此处查看原题)

树形DP写法

看到这个题的要求,很容易相到这是一个树形DP的问题,但是dp数组应该如何设计并转移才是关键

dp[i][0]代表当前结点可以向上覆盖2层,自身一定被覆盖
dp[i][1]代表当前结点可以向上覆盖1层,自身一定被覆盖
dp[i][2]代表当前结点可以向上覆盖0层,自身一定被覆盖
dp[i][3]代表当前结点可以向下覆盖1层,表示自己不一定被覆盖,但是儿子一定全部被覆盖
dp[i][4]代表当前结点可以向下覆盖2层,表示自己不一定被覆盖,但是孙子一定全部被覆盖

所谓向上覆盖x层,即当前结点向上x个结点(祖先结点)作为根结点的树被完全覆盖的情况下,最少需要设立的消防站数目
所谓向下覆盖x层,即当前结点向下x个结点(后代结点)作为根结点的树被完全覆盖的情况下,最少需要设立的消防站数目

显然满足
dp[i][0] >= dp[i][1] >= dp[i][2] >= dp[i][3] >= dp[i][4]

dp[u][0] = 1 + min(dp[v][0~4])
如果当前结点想要覆盖向上2层,则自身必然安置一个消防站,而u所有子节点就随意了

dp[u][1] = min{ dp[v][0] + ∑dp[e][0~3] , dp[u][0] }
覆盖到当前结点上1层,有两种情况:
1)在其子节点安置了至少一个消防站,这样一来,不仅覆盖了当前结点上一层的结点,而且
安置了消防站的子节点将覆盖他的兄弟结点,所以其兄弟结点至少保证自身子节点被完全覆盖即可
2)当前结点安置消防站,覆盖了上两层的同时,也可以覆盖当前结点上一层

dp[u][2] = min{dp[v][1] + ∑dp[e][2],dp[u][1],dp[u][0]}
覆盖到当前结点上0层,分三种情况
1)刚好覆盖到当前结点,则至少选择一个当前结点的孙子结点,由于这个消防站无法覆盖到当前结点的其他子节点,
那么其余结点至少保证自身被覆盖即可
2)可以覆盖到当前结点向上一层,同时覆盖了当前结点的子孙结点,此时即在当前结点的子节点设立消防站
3)可以覆盖到当前结点向上二层,同时覆盖了当前结点的子孙结点,此时即在当前结点设立消防站

dp[u][3] = ∑dp[v][2]
覆盖当前结点所有的子节点,则保证当前结点的所有子节点被覆盖

dp[u][4] = ∑dp[v][3]
覆盖当前结点所有的孙子节点,则保证当前结点的所有子节点的子节点被覆盖

最后,为了覆盖整棵树,我们输出dp[1]0]即可

代码区

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
#include<string>
#include<fstream>
#include<vector>
#include<stack>
#include <map>
#include <iomanip>

#define bug cout << "**********" << endl
#define show(x, y) cout<<"["<<x<<","<<y<<"] "
#define LOCAL = 1;
using namespace std;
typedef long long ll;
const int inf = 1e9 + 7;
const int mod = 1e9 + 7;
const int Max = 1e3 + 10;

int n, m, k;
int head[Max], tot;
int to[Max], Next[Max];
int dp[Max][5];

/*
 * dp[i][0]代表当前结点可以向上覆盖2层,自身一定被覆盖
 * dp[i][1]代表当前结点可以向上覆盖1层,自身一定被覆盖
 * dp[i][2]代表当前结点可以向上覆盖0层,自身一定被覆盖
 * dp[i][3]代表当前结点可以向下覆盖1层,表示自己不一定被覆盖,但是儿子一定全部被覆盖
 * dp[i][4]代表当前结点可以向下覆盖2层,表示自己不一定被覆盖,但是孙子一定全部被覆盖
 *
 * 所谓向上覆盖x层,即当前结点向上x个结点(祖先结点)作为根结点的树被完全覆盖的情况下,最少需要设立的消防站数目
 * 所谓向下覆盖x层,即当前结点向下x个结点(后代结点)作为根结点的树被完全覆盖的情况下,最少需要设立的消防站数目
 *
 * 显然满足
 * dp[i][0] >= dp[i][1] >= dp[i][2] >= dp[i][3] >= dp[i][4]
 *
 * dp[u][0] = 1 + min(dp[v][0~4])
 * 如果当前结点想要覆盖向上2层,则自身必然安置一个消防站,而u所有子节点就随意了
 *
 * dp[u][1] = min{ dp[v][0] + ∑dp[e][0~3] , dp[u][0] }
 * 覆盖到当前结点上1层,有两种情况:
 * 1)在其子节点安置了至少一个消防站,这样一来,不仅覆盖了当前结点上一层的结点,而且
 * 安置了消防站的子节点将覆盖他的兄弟结点,所以其兄弟结点至少保证自身子节点被完全覆盖即可
 * 2)当前结点安置消防站,覆盖了上两层的同时,也可以覆盖当前结点上一层
 *
 * dp[u][2] = min{dp[v][1]  +  ∑dp[e][2],dp[u][1],dp[u][0]}
 * 覆盖到当前结点上0层,分三种情况
 * 1)刚好覆盖到当前结点,则至少选择一个当前结点的孙子结点,由于这个消防站无法覆盖到当前结点的其他子节点,
 * 那么其余结点至少保证自身被覆盖即可
 * 2)可以覆盖到当前结点向上一层,同时覆盖了当前结点的子孙结点,此时即在当前结点的子节点设立消防站
 * 3)可以覆盖到当前结点向上二层,同时覆盖了当前结点的子孙结点,此时即在当前结点设立消防站
 *
 * dp[u][3] = ∑dp[v][2]
 * 覆盖当前结点所有的子节点,则保证当前结点的所有子节点被覆盖
 *
 * dp[u][4] = ∑dp[v][3]
 * 覆盖当前结点所有的孙子节点,则保证当前结点的所有子节点的子节点被覆盖
 *
 * 最后,为了覆盖整棵树,我们输出dp[1]0]即可
 */

void add(int u, int v)
{
    to[tot] = v;
    Next[tot] = head[u];
    head[u] = tot++;
}

void dfs(int u)
{
    dp[u][0] = 1;
    dp[u][1] = inf;
    dp[u][2] = inf;
    dp[u][3] = 0;
    dp[u][4] = 0;
    for (int i = head[u]; i != -1; i = Next[i])
    {
        int v = to[i];
        dfs(v);
        dp[u][0] += dp[v][4];
        dp[u][3] += dp[v][2];
        dp[u][4] += dp[v][3];
    }
    if (head[u] == -1)                //没有子节点了,此时dp[u][0~2]必须使得u安置消防站
    {
        dp[u][1] = dp[u][2] = 1;
        return;
    }
    for (int i = head[u]; i != -1; i = Next[i])
    {
        int v = to[i];

        int sum1 = 0, sum2 = 0;        //记录∑dp[e][3]和∑dp[e][4]
        for (int j = head[u]; j != -1; j = Next[j])
        {
            int e = to[j];
            if (e == v)
                continue;
            sum1 += dp[e][3];
            sum2 += dp[e][2];
        }
        dp[u][1] = min(dp[u][1], dp[v][0] + sum1);
        dp[u][2] = min(dp[u][2], dp[v][1] + sum2);
    }
    for (int i = 1; i <= 4; i++)    //最后综合处理一下
        dp[u][i] = min(dp[u][i], dp[u][i - 1]);
}

int main()
{
#ifdef LOCAL
//    freopen("input.txt", "r", stdin);
//    freopen("output.txt", "w", stdout);
#endif
    memset(head, -1, sizeof(head));
    tot = 0;

    scanf("%d", &n);
    for (int v = 2, u; v <= n; v++)
        scanf("%d", &u), add(u, v);
    dfs(1);
    printf("%d\n", dp[1][2]);
    return 0;
}


贪心写法(适用于在树种,求点覆盖半径为k的最小点覆盖)

其实这种解法对于这一类型的题目非常的适合,主要体现在不需要像树形DP一样确定状态并且易于理解,这种解法的思想如下:

我们用dis[i]表示结点i到最近的消防站的最短距离,如果dis[i] > k ,说明结点i不在已存在的消防站的覆盖范围内,为了保证消防站利用率最大化,我们在结点i向上第k个结点,记作x,也就是结点i覆盖的极限范围出处设立消防站,这样一来不仅使得结点i被覆盖,还可以覆盖更多的结点,然后我们更新x向上k层范围内所有结点的dis,即更新各点到消防站的最近距离,重复这一过程,统计消防站的数目即可

代码区

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
#include<string>
#include<fstream>
#include<vector>
#include<stack>
#include <map>
#include <iomanip>

#define bug cout << "**********" << endl
#define show(x, y) cout<<"["<<x<<","<<y<<"] "
#define LOCAL = 1;
using namespace std;
typedef long long ll;
const int inf = 1e9 + 7;
const int mod = 1e9 + 7;
const int Max = 1e3 + 10;

struct Node
{
    int depth;                //记录当前结点深度
    int id;                    //记录结点编号
} node[Max];

int n, k;
int dis[Max];                //dis[i]记录结点i到最近的消防站的最近距离
int fa[Max];                //dp[i]记录i的父节点

bool cmp(Node node1, Node node2)
{
    return node1.depth > node2.depth;
}

int main()
{
#ifdef LOCAL
    //    freopen("input.txt", "r", stdin);
    //    freopen("output.txt", "w", stdout);
#endif
    scanf("%d", &n);

    k = 2;                              //结点覆盖半径为2,根据实际情况改变

    node[1].depth = 0;
    node[1].id = 1;
    dis[0] = dis[1] = inf;              //处理根结点(这里灵活建图即可)

    for (int v = 2, u; v <= n; v++)
    {
        scanf("%d", &u);                //构建的边为 u --> v
        node[v].depth = node[u].depth + 1;
        node[v].id = v;
        fa[v] = u;
        dis[v] = inf;
    }
    sort(node + 1, node + 1 + n, cmp);
    int sum = 0;

    for (int i = 1; i <= n; i++)
    {
        int son = node[i].id;
        int now = node[i].id;           //当前结点

        for (int j = 1; j <= k; j++)    //处理出k个祖先结点到当前结点的最佳距离
        {
            now = fa[now];
            dis[son] = min(dis[son], dis[now] + j);
        }

        if (dis[son] > k)                //代表每个结点覆盖半径k
        {
            dis[now] = 0;                //此处总是在最远祖先处设立消防站
            sum++;
            for (int j = 1; j <= k; j++) //之后由最远祖先结点向上k层更新其余点的距离
            {
                now = fa[now];
                dis[now] = min(dis[now], j);
            }
        }
    }
    printf("%d\n", sum);
    return 0;
}

原文地址:https://www.cnblogs.com/winter-bamboo/p/11516016.html

时间: 2024-11-01 06:34:18

P2279 消防局的设立 (树形DP or 贪心)的相关文章

bzoj1217: [HNOI2003]消防局的设立 [树形dp]

Description 2020年,人类在火星上建立了一个庞大的基地群,总共有n个基地.起初为了节约材料,人类只修建了n-1条道路来连接这些基地,并且每两个基地都能够通过道路到达,所以所有的基地形成了一个巨大的树状结构.如果基地A到基地B至少要经过d条道路的话,我们称基地A到基地B的距离为d.由于火星上非常干燥,经常引发火灾,人类决定在火星上修建若干个消防局.消防局只能修建在基地里,每个消防局有能力扑灭与它距离不超过2的基地的火灾.你的任务是计算至少要修建多少个消防局才能够确保火星上所有的基地在

【贪心】P3942 将军令 &amp;&amp; P2279 消防局的设立

1.消防局的设立 题目描述 2020年,人类在火星上建立了一个庞大的基地群,总共有n个基地.起初为了节约材料,人类只修建了n-1条道路来连接这些基地,并且每两个基地都能够通过道路到达,所以所有的基地形成了一个巨大的树状结构.如果基地A到基地B至少要经过d条道路的话,我们称基地A到基地B的距离为d. 由于火星上非常干燥,经常引发火灾,人类决定在火星上修建若干个消防局.消防局只能修建在基地里,每个消防局有能力扑灭与它距离不超过2的基地的火灾. 你的任务是计算至少要修建多少个消防局才能够确保火星上所有

LuoguP1131 [ZJOI2007]时态同步 (树形DP,贪心)

贪心就离根最大距离 #include <iostream> #include <cstdio> #include <cstring> #include <algorithm> #include <cmath> #define R(a,b,c) for(register int a = (b); a <= (c); ++ a) #define nR(a,b,c) for(register int a = (b); a >= (c); -

某不知名的树形Dp

题意 给出一个\(N\)个点的树,找出一个点来,以这个点为根的树时,所有点的深度之和最大 分析 看到树,还让求最大,这种可能不是贪心就是树形\(DP\),贪心的话树的形状没法判断,果断放弃,那么就只能是\(DP\)了. 既然它让求深度之和,那么我就直接定义以\(i\)为根时深度和为\(DP_i\),接下来就是怎么转移的问题了.如果我枚举每个点来考虑,那么还要计算它下边的子树和它上边的子树,显然是不好弄,时间复杂度可能在\(O(N^2)\)左右,虽然时间十秒但也不够用啊,由于\(n\)大到了100

[luogu]P2279 [HNOI2003]消防局的设立[贪心]

[luogu]P2279 [HNOI2003]消防局的设立 题目描述 2020年,人类在火星上建立了一个庞大的基地群,总共有n个基地.起初为了节约材料,人类只修建了n-1条道路来连接这些基地,并且每两个基地都能够通过道路到达,所以所有的基地形成了一个巨大的树状结构.如果基地A到基地B至少要经过d条道路的话,我们称基地A到基地B的距离为d. 由于火星上非常干燥,经常引发火灾,人类决定在火星上修建若干个消防局.消防局只能修建在基地里,每个消防局有能力扑灭与它距离不超过2的基地的火灾. 你的任务是计算

P2279 [HNOI2003]消防局的设立

P2279 [HNOI2003]消防局的设立考场上想出了贪心策略,但是处理细节时有点问题,gg了.从(当前深度最大的节点)叶子节点往上跳k个,在这里设消防局,并从消防局遍历k个距离,标记上. 1 #include<iostream> 2 #include<cstdio> 3 #include<queue> 4 #include<algorithm> 5 #include<cmath> 6 #include<ctime> 7 #incl

hdu4313 贪心并查集 || 树形dp

http://acm.hdu.edu.cn/showproblem.php?pid=4313 Problem Description Machines have once again attacked the kingdom of Xions. The kingdom of Xions has N cities and N-1 bidirectional roads. The road network is such that there is a unique path between any

Codeforces 77C 树形dp + 贪心

题目链接:点击打开链接 题意: 给定n个点, 每个点的豆子数量 下面是一棵树 再给出起点 每走到一个点,就会把那个点的豆子吃掉一颗. 问:回到起点最多能吃掉多少颗豆子 思路:树形dp 对于当前节点u,先把子节点v都走一次. 然后再往返于(u,v) 之间,直到u点没有豆子或者v点没有豆子. dp[u] 表示u点的最大值.a[u] 是u点剩下的豆子数. #include <cstdio> #include <vector> #include <algorithm> #inc

POJ 2057 The Lost House 经典树形DP+贪心

题意:链接 方法:树形DP+贪心 解析:这是一道好题. 好首先要明确这题求的是什么? 名义上是期望值,而实际上就是找一条路径.什么路径呢?从根节点走遍所有的叶子节点所花费步数最短的路径. 明确了题意后该怎么做呢? 首先看我们需要什么? 目前有个根节点,我们需要知道从他向一个分支走,失败步数是多少,成功步数是多少? 那么怎么维护我们需要的东西呢? 首先我们先给他们起个名:suc,fai; 其次再给一个节点的叶子节点的个数起个名:son 起名完事之后我们就要更新了. 先谈叶子节点,显然叶子节点的su