P2279 [HNOI2003]消防局的设立 -------树形dp,详细题解

题目

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

你的任务是计算至少要修建多少个消防局才能够确保火星上所有的基地在发生火灾时,消防队有能力及时扑灭火灾。
输入
输入文件的第一行为n (n<=1000),表示火星上基地的数目。接下来的n-1行每行有一个正整数,其中文件第i行的正整数为a[i],表示从编号为i的基地到编号为a[i]的基地之间有一条道路,为了更加简洁的描述树状结构的基地群,有a[i]<i。

样例输入

6
1
2
3
4
5
样例输出
2

这个题一定与树有关,这题是可以贪心过的,但是这么好的题不来dp是不是有点可惜,所以写一下树形dp,网上很多题解讲的都不清楚,我来个清楚一点的。
二维dp
dp[x][0]代表选取这个节点作为消防局使得这个深度为三的子树完全被覆盖的最小消防局的数量。
dp[x][1]代表选取某个儿子来覆盖这个点(x)使得这个子树完全被覆盖的最小消防局的数量。
dp[x][2]代表选取某个孙子节点作为消防局来覆盖这个点(x)使得这个子树完全被覆盖的最下数量。
dp[x][3]代表使得所有儿子和孙子节点被覆盖的最小的数量。
这个地方要知道是因为一个消防局可以覆盖的距离为2,所以我们目前可以不覆盖这个点x,让它的父亲或者爷爷来覆盖它。
dp[x][4]代表使得所有孙子被覆盖的最小数量。 同理。
因为是一层一层从下往上dp的所以dp[x][0]其实会使得dp[x][1]的情况满足,仔细理解一下。(就是选取这个点当消防局一定是满足他儿子那一颗子树已经是完全覆盖的,即儿子的孙子已经被覆盖)
接下来就可以开始dp了

dp[x][0]这个状态推过来要保证子树覆盖,因为这个点可以直接覆盖孙子,所以要考虑到儿子的孙子是不是被覆盖了,dp[x][0] = ∑min(dp[j][0…4])(j代表一个子节点)
dp[x][1] 选取了一个儿子,所有所有儿子和根被覆盖,需要满足的是覆盖掉儿子的孙子,所以do[x][1] = min(dp[k][0] + ∑mindp[j][0…3]) ,) (k 代表选取的那个子节点,j代表其他的儿子节点)
dp[x][2]选取了一个孙子,只有这个孙子的父亲和兄弟以及根节点被覆盖,所以需要让这个孙子的父亲的兄弟的这些子树被覆盖,包括父亲的兄弟,通过上面的包含关系,要覆盖只能选择0-2,所以do[x][2] = min(dp[k][1] + ∑mindp[j][0…2]) (k 代表选取的那个子节点,j代表其他的儿子节点)

剩下两个就是需要求覆盖子树不包含父节点的就可以,保证根节点儿子的子树覆盖,因为根节点的父亲可以向下覆盖。

下面上代码

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

using namespace std;

const int maxn = 10010;

struct node
{
    int to, next;
};

struct data
{
    int brother, son;
};

int tot;
node edge[maxn];
data rela[maxn];
int dp[maxn][6];
int head[maxn];

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

void dfs(int u, int fa)
{
    for(int i = head[u]; ~i; i = edge[i].next)
    {
        int v = edge[i].to;
        if(v != fa)
        {
            dfs(v, u);
            rela[v].brother = rela[u].son;
            rela[u].son = v;
        }
    }
}

int finmin(int x, int l, int r)
{
    int minn = 0x3f3f3f3f;
    for(int i = l; i <= r; i++)
        minn = min(minn, dp[x][i]);
    return minn;
}

void Dp(int x)
{
    if(rela[x].son == 0)
    {
        dp[x][0] = 1;
        dp[x][1] = 1;
        dp[x][2] = 1;
        return;
    }
    for(int i = rela[x].son; i != 0; i = rela[i].brother)
    {
        Dp(i);
    }
    dp[x][0] = 1;
    for(int i = rela[x].son; i; i = rela[i].brother)
    {
        int to = i;
        dp[x][0] += finmin(to, 0, 4);
    }
    int mm = 0x3f3f3f3f;
    for(int i = rela[x].son; i; i = rela[i].brother)
    {
        int to = i;
        int all = 0;
        for(int j = rela[x].son; j != 0; j = rela[j].brother)
        {
            if(j != to)
            all += finmin(j, 0, 3);
        }
        mm = min(mm, all + dp[to][0]);
    }
    dp[x][1] = mm;
    mm = 0x3f3f3f3f;
     for(int i = rela[x].son; i; i = rela[i].brother)
    {
        int to = i;
        int all = 0;
        for(int j = rela[x].son; j != 0; j = rela[j].brother)
        {
            if(j != to)
            all += finmin(j, 0, 2);
        }
        mm = min(mm, all + dp[to][1]);
    }
    dp[x][2] = mm;
    for(int i = rela[x].son; i != 0; i = rela[i].brother)
    {
        dp[x][3] += finmin(i, 0, 2);
        dp[x][4] += finmin(i, 0, 3);
    }

}

int main()
{
    int n;
    //freopen("input.txt", "r", stdin);
    //freopen("out.txt", "w", stdout);
    memset(head, -1, sizeof(head));
    scanf("%d", &n);
    for(int i = 2; i <= n; i++)
    {
        int u;
        scanf("%d", &u);
        add(u, i);
        add(i, u);
    }
    dfs(1, 0);
    Dp(1);
    printf("%d\n", finmin(1, 0, 2));
    return 0;

}

有错误请指正,或者有疑问可以问一下。

发布了40 篇原创文章 · 获赞 13 · 访问量 865

猜你喜欢

转载自blog.csdn.net/weixin_43891021/article/details/100011554
今日推荐