题解-luogu-p2279消防局的设立

题目链接

给定一棵有 $n$ 个节点的树,求至少需要标记多少个点使得树上任意两个点的距离均小于等于 $2$

$n \le 1000$

感谢@oy的贡献

实际上就是这道题的简化版。

首先定义状态。$f[u][4]$ 表示节点 $u$ 的二级祖先(父亲的父亲)及以下节点被完全覆盖,所需的最小代价。$f[u][3]$ 表示节点 $u$ 的一级祖先及以下节点被完全覆盖所需最小代价。以此类推, $f[u][0]$ 表示节点 $u$ 的二级儿子(儿子的儿子)及以下节点被完全覆盖所需的最小代价。

考虑 $f[u][4]$ 的推导。由于每个节点被选中后只能覆盖到与其距离小于等于二的节点,要使 $u$ 的二级祖先被覆盖到,则节点 $u$ 必须被选取。对节点 $u$ 的各个儿子没有要求。因此,$f[u][4]=1+\Sigma f[v][0…4]$ 。

考虑 $f[u][3]$。由于只需要覆盖到节点 $u$ 的父亲,只需使节点 $u$ 的至少一个子节点可以覆盖到其二级祖先即可。同时,该子节点在覆盖到节点 $u$ 的二级祖先时,可以同时覆盖到节点 $u$ 的其他儿子,因此节点 $u$ 的其他儿子不必被覆盖。$f[u][3]=f[k][4]+\Sigma f[v][1…4]$。

$f[u][2]$ 的情况与 $f[u][3]$ 类似,只需保证一个儿子能将节点 $u$ 覆盖即可。 $f[u][2]=f[k][3]+\Sigma f[v][2…4]$ 。

$f[u][1]$ 与 $f[u][0]$ 的推导相对简单,因为各个子节点之间不会相互影响。 $f[u][1]=\Sigma f[v][2…4]$,$f[u][0]=\Sigma f[v][1…4]$。

对转移方程进行简单优化即可。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include<bits/stdc++.h>
using namespace std;
vector<int> G[1005];
int N;
int f[1005][10];

void dp(int u, int fa) {
f[u][3] = 0x3f3f3f3f;
f[u][2] = 0x3f3f3f3f;
f[u][4] = 1;
for (vector<int>::iterator it = G[u].begin(); it != G[u].end(); it++) {
int v = *it;
if (v == fa) continue;
dp(v, u);
f[u][0] += f[v][1];
f[u][1] += f[v][2];
f[u][2] = min(f[u][2], f[v][3] - f[v][2]);
f[u][3] = min(f[u][3], f[v][4] - f[v][1]);
f[u][4] += f[v][0];
}
f[u][2] += f[u][1];
f[u][3] += f[u][0];
f[u][3] = min(f[u][3], f[u][4]);
f[u][2] = min(f[u][2], f[u][3]);
f[u][1] = min(f[u][1], f[u][2]);
f[u][0] = min(f[u][0], f[u][1]);


}

int main() {
scanf("%d", &N);
for (register int u = 2; u <= N; ++u) {
int v;
scanf("%d", &v);
G[u].push_back(v);
G[v].push_back(u);
}
dp(1, 0);
printf("%d", f[1][2]);
return 0;
}

Comments

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×