树形DP入门 洛谷P2279 [HNOI2003]消防局的设立 详解

19 篇文章 0 订阅

洛谷P2279 [HNOI2003]消防局的设立

树形DP

所谓树形DP,即就是在树上进行DP

动态转移为由子树向父亲节点转移

一般实现形式为dfs

void dfs(int x) {
	for (int i = 0; i < E[x].size(); i++) {
		dfs(E[x][i]);
        //寻找并对子树DP
        //---------------
	}
	if (E[x].size() == 0) {
		//给叶子节点赋初值
        //----------------
	}
	else{
		//由子树向节点转移的动态转移方程
        //----------------
	}
}

题意

题目描述

2020年,人类在火星上建立了一个庞大的基地群,总共有n个基地。起初为了节约材料,人类只修建了n-1条道路来连接这些基地,并且每两个基地都能够通过道路到达,所以所有的基地形成了一个巨大的树状结构。如果基地A到基地B至少要经过d条道路的话,我们称基地A到基地B的距离为d。

由于火星上非常干燥,经常引发火灾,人类决定在火星上修建若干个消防局。消防局只能修建在基地里,每个消防局有能力扑灭与它距离不超过2的基地的火灾。

你的任务是计算至少要修建多少个消防局才能够确保火星上所有的基地在发生火灾时,消防队有能力及时扑灭火灾。

输入输出格式

输入格式:

输入文件名为input.txt。

输入文件的第一行为n (n<=1000),表示火星上基地的数目。接下来的n-1行每行有一个正整数,其中文件第i行的正整数为a[i],表示从编号为i的基地到编号为a[i]的基地之间有一条道路,为了更加简洁的描述树状结构的基地群,有a[i]<i。

输出格式:

输出文件名为output.txt

输出文件仅有一个正整数,表示至少要设立多少个消防局才有能力及时扑灭任何基地发生的火灾。

输入输出样例

输入样例#1: 复制

6
1
2
3
4
5

输出样例#1: 复制

2

分析

这是一道经典的树形DP例题

一般就是给你一棵数和每个点可向外覆盖的点的个数,求最少选几个点可以覆盖所有的点

该类题状态为一般为:1.第几号点

                                    2.还可以向外覆盖多少点

本题可以外覆2个点,递推过程中公有5个状态

状态

dp[i][0]:覆盖其子树以及他本身、他的父亲以及他的父亲的父亲(爷爷?)

dp[i][1]:覆盖其子树以及它本身以及他的父亲

dp[i][2]:覆盖其子树以及他本身

dp[i][3]:覆盖其所有子数

dp[i][4]:覆盖其所有儿子的子树

状态转移方程

i:表示当前节点       s:表示其儿子    k:表示其其他儿子

dp[i][0]=sum(dp[s][4])+1

覆盖本身节点、其父亲以及爷爷需要在其本身设立新的消防站,则其儿子和孙子也已经被覆盖

则覆盖子树只需要将其孙子的子树都覆盖即可。也就是sum(dp[s][4])
dp[i][1]=min(dp[s][0]+sum(dp[k][3]),dp[i][0])

覆盖其父亲,只要有一个子树能覆盖到其爷爷,

其他子树的本身节点也可以由该子树覆盖,其他子树只需要覆盖到其儿子即可。

dp[i][2]=min(dp[s][1]+sum(dp[k][2]),dp[i][1])

覆盖其本身,只要有一个子树能覆盖到其父亲,

其他子树只需要覆盖到其本身即可。

dp[i][3]=min(sum(dp[s][2]),dp[i][2])

覆盖到其儿子,只需要其所有子树覆盖到本身即可
dp[i][4]=min(sum(dp[s][3]),dp[i][3])

覆盖到其孙子,只需要其所有子树覆盖到儿子即可

代码 

#include <iostream>
#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
const int maxn = 1005;
const int inf = 0X3f3f3f3f;
int n;
vector<int> E[maxn];
int dp[maxn][5];
//0 为+2	dp[i][0]=sum(dp[s][4])
//1 为+1	dp[i][1]=min(dp[s][0]+sum(dp[k][3]),dp[i][0])
//2 为0		dp[i][2]=min(dp[s][1]+sum(dp[k][2]),dp[i][1])
//3 为-1	dp[i][3]=sum(dp[s][2])
//4 为-2	dp[i][4]=sum(dp[s][3])
void dfs(int x) {
	dp[x][0] = 1;
	dp[x][1] = dp[x][2] = dp[x][3] = dp[x][4] = 0;
	for (int i = 0; i < E[x].size(); i++) {
		dfs(E[x][i]);
		dp[x][0] += dp[E[x][i]][4];
		dp[x][3] += dp[E[x][i]][2];
		dp[x][4] += dp[E[x][i]][3];
	}
	if (E[x].size() == 0) {
		dp[x][1] = dp[x][2] = 1;
	}
	else{
		for (int i = 0; i < E[x].size(); i++) {
			dp[x][1] += dp[E[x][i]][3];
			dp[x][2] += dp[E[x][i]][2];
		}
		int f1 = inf, f2 = inf;
		for (int i = 0; i < E[x].size(); i++) {
			f1 = min(f1, dp[E[x][i]][0] - dp[E[x][i]][3]);
			f2 = min(f2, dp[E[x][i]][1] - dp[E[x][i]][2]);
		}
		dp[x][1] += f1;
		dp[x][2] += f2;
	}
	for (int i = 1; i <= 4; i++)
		dp[x][i] = min(dp[x][i], dp[x][i - 1]);
}
int main() {
	scanf("%d", &n);
	int f;
	for (int i = 2; i <= n; i++) {
		scanf("%d", &f);
		E[f].push_back(i);
	}
	dfs(1);
	printf("%d\n", dp[1][2]);
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值