题目描述

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

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

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

解析

这道题似乎可以贪心,但是窝是为了dp而来的,显然这是个树形dp。

定义状态:

\(dp[0][x]\)表示消防局已经覆盖了以\(x\)为根节点的子树和该节点的父节点和爷爷节点,可以看作该点有消防局;

\(dp[1][x]\)表示已经至少覆盖了以\(x\)为根节点的子树和该节点的父节点,可以看作该点的儿子有消防局;

\(dp[2][x]\)表示已经至少覆盖的以\(x\)为根节点的子树,可以看作该点的孙子节点有消防局;

\(dp[3][x]\)表示已经至少覆盖了所有以\(x\)的儿子为根节点的子树;

\(dp[4][x]\)表示已经至少覆盖了所有以\(x\)的孙子为根节点的子树,后面这俩都是用来给上面状态的转移作中介的。

务必明确一点:实际上对于某个状态,我们刻画其为以某点为根的子树子树与父节点、爷爷节点都被覆盖,并且子树包含根节点。

对于状态\(dp[0][x]\),显然在\(x\)节点就必须有一个消防局,才能满足状态定义。假设其儿子节点的集合为\(t\),且有\(y\in t\),为了使得答案最优,我们直接从\(dp[4][y]\)转移过来。

也就是:\(dp[0][x]=1+\sum_{y \in t}dp[4][y]\)

对于状态\(dp[1][x]\),显然在\(x\)的某个儿子节点上必须要有一个消防局,即\(dp[0][y]\),此时其它儿子节点都没有被覆盖时最优的,即\(dp[3][y]\)。由于状态\(dp[0][x]\)包含了这个状态,于是我们把它也加进来取\(\min\)。

沿用上面的假设,我们得到:\(dp[1][x]=\min\limits_{y\in t,z\in t且z\ne y}\{dp[0][y]+\sum dp[3][z],dp[0][x]\}\)

对于状态\(dp[2][x]\),与二相似,在\(x\)的某个孙子节点上必须要有一个消防局,即\(dp[1][y]\),此时其它儿子节点都恰好为状态\(dp[2][y]\)时为最优解。由于状态\(dp[0/1][x]\)都包含了这个状态,于是我们把它们也加进来取\(\min\)。

沿用上面的假设,我们得到:\(dp[2][x]=\min\limits_{y\in t,z\in t且z\ne y}\{dp[1][y]+\sum{dp[2][z]},dp[0/1][x]\}\)

对于状态\(dp[3][x]\),我们直接像\(dp[0][x]\)一样,加上所有\(dp[2][y]\)就行。然后类比上面三条,同理,\(dp[0/1/2][x]\)显然包含该状态。

沿用上面的假设,我们得到:\(dp[3][x]=\min\limits_{y\in t}\{\sum dp[2][y],dp[0/1/2][x]\}\)

同上。

注意:叶子节点和叶子节点的父节点,它们的\(dp[1][x],dp[2][x]\)状态必须有一个消防局,否则不满足状态定义,因此初始化的时候要稍微注意一下。


这道题就变得简单了。

参考代码

#include<cstdio>
#include<iostream>
#include<cmath>
#include<cstring>
#include<ctime>
#include<cstdlib>
#include<algorithm>
#include<queue>
#include<set>
#include<map>
#define N 1010
#define INF 0x7fffffff
using namespace std;
struct node{
int next,ver;
}g[N<<1];
int tot,head[N],n;
int dp[5][N];
inline void add(int x,int y)
{
g[++tot].ver=y;
g[tot].next=head[x],head[x]=tot;
}
inline void dfs(int x)
{
dp[0][x]=1;dp[3][x]=0;dp[4][x]=0;
for(int i=head[x];i;i=g[i].next){
int y=g[i].ver;
dfs(y);
dp[0][x]+=dp[4][y];
dp[3][x]+=dp[2][y];
dp[4][x]+=dp[3][y];
}
if(head[x]==0){
dp[1][x]=dp[2][x]=1;
}
else{
dp[1][x]=dp[2][x]=INF;
for(int i=head[x];i;i=g[i].next){
int y=g[i].ver;
int t1=dp[0][y],t2=dp[1][y];
for(int j=head[x];j;j=g[j].next){
if(i==j) continue;
int z=g[j].ver;
t1+=dp[3][z];
t2+=dp[2][z];
}
dp[1][x]=min(dp[1][x],t1);
dp[2][x]=min(dp[2][x],t2);
}
}
for(int i=1;i<=4;++i)
dp[i][x]=min(dp[i][x],dp[i-1][x]);
}
int main()
{
scanf("%d",&n);
for(int i=2;i<=n;++i){
int u;
scanf("%d",&u);
add(u,i);
}
dfs(1);
cout<<dp[2][1]<<endl;
return 0;
}

P2279 [HNOI2003]消防局的设立[树形dp]的更多相关文章

  1. 【BZOJ1217】[HNOI2003]消防局的设立 树形DP

    [BZOJ1217][HNOI2003]消防局的设立 Description 2020年,人类在火星上建立了一个庞大的基地群,总共有n个基地.起初为了节约材料,人类只修建了n-1条道路来连接这些基地, ...

  2. [HNOI2003]消防局的设立 树形dp // 贪心

    https://www.luogu.org/problemnew/show/P2279 一开始就想到了贪心的方法,不过一直觉得不能证明. 贪心的考虑是在深度从深到浅遍历每个结点的过程中,对于每个没有覆 ...

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

    Description 2020年,人类在火星上建立了一个庞大的基地群,总共有n个基地.起初为了节约材料,人类只修建了n-1条道路来连接这些基地,并且每两个基地都能够通过道路到达,所以所有的基地形成了 ...

  4. luogu 2279 [HNOI2003]消防局的设立 树形dp

    就是细节多一些,思路都非常常规. Code: #include <bits/stdc++.h> #define N 1005 #define inf 1061109567 #define ...

  5. [HNOI2003] 消防局的设立 - 树形dp

    仍然是点覆盖集问题,但覆盖半径变成了\(2\) 延续上一题的思路,只是式子更加复杂了 想体验一下min_element大法于是不想优化了 #include <bits/stdc++.h> ...

  6. P2279 [HNOI2003]消防局的设立

    P2279 [HNOI2003]消防局的设立考场上想出了贪心策略,但是处理细节时有点问题,gg了.从(当前深度最大的节点)叶子节点往上跳k个,在这里设消防局,并从消防局遍历k个距离,标记上. #inc ...

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

    [luogu]P2279 [HNOI2003]消防局的设立 题目描述 2020年,人类在火星上建立了一个庞大的基地群,总共有n个基地.起初为了节约材料,人类只修建了n-1条道路来连接这些基地,并且每两 ...

  8. P2279 [HNOI2003]消防局的设立 贪心or树形dp

    题目描述 2020年,人类在火星上建立了一个庞大的基地群,总共有n个基地.起初为了节约材料,人类只修建了n-1条道路来连接这些基地,并且每两个基地都能够通过道路到达,所以所有的基地形成了一个巨大的树状 ...

  9. 洛谷 P2279 [HNOI2003]消防局的设立 (树形dp or 贪心)

    一看到这道题就知道是树形dp 之前做过类似的题,只不过保护的范围是1 所以简单很多. 这道题保护的范围是2,就复杂了很多. 我就开始列状态,然后发现竟然有5种 然后我就开始列方程. 但是我考虑的时候是 ...

随机推荐

  1. 【视频开发】关于FFMPEG中内存泄漏的问题之av_bitstream_filter_filter

    How may I free pkt in an ffmpeg write frame method Rate this:      See more: C++ ffmpeg Greetings I' ...

  2. 1、快速搭建后台list

    @RestController @CrossOrigin public class UserController { @Resource private UserService userService ...

  3. 笨方法学Python摘记(1)

    编程新手所需的最重要的三种技能:读和写.注重细节.发现不同 不要复制粘贴! #-*-codinig:utf-8 -*-  (脚本使用unicode UTF-8) 书写习惯:操作符的两边加上空格,提高代 ...

  4. This view is not constrained, it only has designtime positions, so it will jump to (0,0) unless you

    Android Studio报错 这个视图只是编辑时位置,在运行时视图会跳转到(0,0) 解决办法: 在Design界面下,有个魔棒工具,Infer Constrains,点击之后就可以了

  5. 【C++札记】指针数组和数组指针

    指针数组: 存储指针的数组,数组找那个的每个一元素都是指针 例: int* p1[4],p2[0]是一个指向int类型的指针 char* p2[4],p1[0]是一个指向char类型的指针 数组指针: ...

  6. 用Python实现扑克牌面试题思路

    据说是腾讯的面试题,以下是要求: 一副从1到n的牌,每次从牌堆顶取一张放桌子上,再取一张放牌堆底,直到手中没牌.根据桌上的牌堆顺序,输出原先手中牌堆的顺序数组. 实现思路: 1.首先定义一个2维数组, ...

  7. 『Go基础』第6节 注释

    在上一节中, 我们学会了怎样写一个 Hello Go . 但是, 大家有可能还没有明白为什么那么写, 下面我们通过注释来了解一下. 注释的重要性不再过多赘述, 一段不写注释的代码读起来实在难受. 那么 ...

  8. 流程审批时执行BE插件

    1.启用审批流时,BE插件解决方案目标框架必须采用.Net Framwork3.5: 2.BE插件相关DLL部署位置:Applicationser/libs.MailServer/libs: 3.BE ...

  9. Spring AOP创建Throwdvice实例

    1.异常发生的时候,通知某个服务对象做处理 2.实现throwsAdvice接口 接口实现: public interface IHello { public void sayHello(String ...

  10. 强大的VIM

    个人感觉,vim用熟了,比任何编辑器都好用,VIM的许多特性节省了时间和击键次数,并可以完成一些其他编辑器无法完成的功能,这里在网上找了几个经典案例,记录一下. 与大部分其它编辑器不同,进入 Vim ...