考虑树形dp,设 $dp[i][j], j \in [0, 2]$ 含义为,以 $i$ 为根节点的子树中,向下 $j$ 个位置设有基地的最小花费。
$j = 0$ 时,代表在当前节点设立,那么距离 $i$ 的所有子节点 $j$ 中, $d_j \le 3$ 的节点 $dp[j][k]$ 无论 $k$ 取何值,都可以满足限制。
$j = 1$ 时,代表在任意距离 1 的子树中设立,那么其余距离 $i$ 的所有子节点 $j$ 中,只要距离在 2 以内,无论 $k$ 取何值,都可以满足限制。
$j = 2$ 时,那就考虑在子树内继承某个 $dp[j][1]$ 然后把其余的子树代价加起来即可。
#include <iostream>
#include <vector>
#include <cstring>
using namespace std;
#define MAX_N 1005
int idle = 1;
int head[MAX_N] = { 0 }, nex[MAX_N << 1] = { 0 };
int edge[MAX_N << 1] = { 0 };
void add(int u, int v) {
nex[idle] = head[u];
head[u] = idle;
edge[idle++] = v;
}
vector<int> tr[MAX_N];
int dp[MAX_N][3] = { 0 };
void dfs1(int u, int fa) {
for (int ind = head[u]; ind; ind = nex[ind]) {
int v = edge[ind];
if (v == fa) continue;
tr[u].push_back(v);
dfs1(v, u);
}
}
//获取距离 == dis 的任意选择的节点和
int get_s(int u, int d, int dis) {
if (dis == d) return min(dp[u][0], min(dp[u][1], dp[u][2]));
else {
if (!tr[u].size()) return 0;
int s = 0;
for (auto ch : tr[u]) s += get_s(ch, d + 1, dis);
return min(s, min(dp[u][0], min(dp[u][1], dp[u][2])));
}
}
void dfs2(int u) {
int s0 = 0;
int m = tr[u].size();
for (auto ch : tr[u]) dfs2(ch);
for (auto ch : tr[u]) s0 += get_s(ch, 1, 3);
dp[u][0] = s0 + 1;
if (m) { //有孩子才能考虑 1 和 2
int s1 = 0, s2 = 0;
for (auto ch : tr[u]) s1 += get_s(ch, 1, 2);
for (auto ch : tr[u]) {
dp[u][1] = min(dp[u][1], s1 - get_s(ch, 1, 2) + dp[ch][0]);
s2 += get_s(ch, 1, 1);
}
for (auto a : tr[u]) {
int curs = get_s(a, 1, 1);
dp[u][2] = min(dp[u][2], s2 - curs + dp[a][1]);
}
}
}
int main() {
int n;
scanf("%d", &n);
for (int x = 2; x <= n; x++) {
int y;
scanf("%d", &y);
add(x, y), add(y, x);
}
memset(dp, 0x3f, sizeof dp);
dfs1(1, 0);
dfs2(1);
printf("%d", min(dp[1][0], min(dp[1][1], dp[1][2])));
return 0;
}