题目描述
2020 年,人类在火星上建立了一个庞大的基地群,总共有 nn 个基地。起初为了节约材料,人类只修建了 n-1n−1 条道路来连接这些基地,并且每两个基地都能够通过道路到达,所以所有的基地形成了一个巨大的树状结构。如果基地 AA 到基地 BB 至少要经过 dd 条道路的话,我们称基地A到基地B的距离为 dd。
由于火星上非常干燥,经常引发火灾,人类决定在火星上修建若干个消防局。消防局只能修建在基地里,每个消防局有能力扑灭与它距离不超过 22 的基地的火灾。
你的任务是计算至少要修建多少个消防局才能够确保火星上所有的基地在发生火灾时,消防队有能力及时扑灭火灾。
输入格式
输入文件的第一行为 nn(1 \leq n \leq 10001≤n≤1000),表示火星上基地的数目。接下来的 n-1n−1 行每行有一个正整数,其中文件第 ii 行的正整数为 a_iai,表示从编号为 ii 的基地到编号为 a_iai 的基地之间有一条道路,为了更加简洁的描述树状结构的基地群,有 a_i\lt iai<i。
输出格式
仅有一个正整数,表示至少要设立多少个消防局才有能力及时扑灭任何基地发生的火灾。
输入输出样例
输入 #1
6 1 2 3 4 5
输出 #1
2
所以按照树形DP的常规套路,我们把一棵子树的状态作为一个划分状态的标准。但是一个子树可能还可以向外覆盖点,也有可能有几个点没覆盖。
由于覆盖半径是2,对于一棵子树划分5种状态:
f{i,0}向上覆盖2个。
f{i,1}向上覆盖1个。
f{i,2}正好覆盖完子树。
f{i,3}覆盖完儿子。
f{i,4}覆盖完孙子。
需要的消防局数目。
f{i,0}选了自己,所以孙子会被覆盖到。要求曾孙全部被覆盖,儿子0-4的状态都满足。
f{i,1}选了儿子中的一个节点,所以其他儿子也会被覆盖到。但是其他儿子的儿子 无法被覆盖到。所以在转移时,随机选取一个点放消防站,并要求其他点的儿子被覆盖到。
同理有
要让根节点的儿子都被覆盖,就是让它们本身都覆盖(好zz啊)
同理有
加速
取[0,2]的最小值,就是覆盖自身时的答案。
对于状态0,[0,4]中显然4最优
同理有
此时0,3和4的状态处理完成,可以利用它们优化1和2的转移。
状态1,可以让所有孙子被覆盖,然后取f{i.child,0}-f{i.child,3}的最小值。这个式子相当于覆盖与i相邻的点需要的消防站数目。
所以有
同理有
最后,你会发现还是贪心好用
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=1005;
int n,t1,t2;
int f[N][5];
bool t[N][N];
int main(){
scanf("%d",&n);
for(int i=2;i<=n;i++){
int x;
scanf("%d",&x);
t[x][i]=1;
}
for(int i=n;i>=1;i--){
f[i][0]=1;
t1=1919,t2=1919;
for(int p=1;p<=n;p++){
if(t[i][p]){
f[i][3]+=f[p][2];
f[i][4]+=f[p][3];
f[i][0]+=f[p][4];
t1=min(t1,f[p][0]-f[p][3]);
t2=min(t2,f[p][1]-f[p][2]);
}
}
f[i][1]=f[i][4]+t1;
f[i][2]=min(f[i][3]+t2,min(f[i][0],f[i][1]));
f[i][3]=min(f[i][2],f[i][3]);
f[i][4]=min(f[i][4],f[i][3]);;
}
printf("%d",f[1][2]);
return 0;
}