原题: https://www.luogu.org/problemnew/show/P2279
题意:
n个点的树,设一个点为消防局,那么离这个点距离2以内的点会被覆盖。问最少的消防局数量使整棵树被覆盖。
树形DP:
先维护好儿子再推父亲的树形dp。
dp[i][0~4]表示遍历到i点时的5种状态:
- dp[i][0]:已经延到i点,且还可以往上2长度
- dp[i][1]:已经延到i点,且还可以往上1长度
- dp[i][2]:已经延到i点,且还可以往上0长度
- dp[i][3]:还未到达i点,父亲节点需要往下延1长度
- dp[i][4]:还未到达i点,父亲节点需要往下延2长度
分析儿子状态对父亲状态的转移:
- dp[fa][0]:自己放一个消费局,所以儿子是什么状态都不用管了,dp[i][0~4]都可以。
- dp[fa][1]:保证有一个儿子的状态为dp[i][0],这个儿子可以延到其他儿子,但不能延到其他儿子的儿子,所以需要其它儿子的dp[i][0~3]。
- dp[fa][2]:保证有一个儿子的状态为dp[i][1],这个儿子不能延到其他儿子,所以需要其他儿子的dp[i][0~2]。
- dp[fa][3]:不考虑自身,但需要保证fa的儿子、孙子全被覆盖,所以dp[i][0~2]。
- dp[fa][4]:不考虑自身和儿子,需要所有孙子都覆盖,所以dp[i][0~3]。
状态转移方程:
可以转化为:
到这里应该已经可以做了,但是因为很多项可以合并以优化时间复杂度。
令
可以发现S只需要234,而dp需要01,可以放在dp上一起使用。
#include<bits/stdc++.h>
using namespace std;
const int N = 1005, M = 2005;
int head[N], nex[M], to[M], now;
void add(int a, int b) {
nex[++now] = head[a];
head[a] = now;
to[now] = b;
}
int dp[N][6];
void dfs(int p, int f) {
int s2 = 0, s3 = 0, s4 = 0, sub03 = 1e9, sub12 = 1e9;
for(int i = head[p]; ~i; i = nex[i]) {
int u = to[i];
if(u == f)
continue;
dfs(u, p);
s2 += dp[u][2];
s3 += dp[u][3];
s4 += dp[u][4];
sub03 = min(sub03, dp[u][0] - dp[u][3]);
sub12 = min(sub12, dp[u][1] - dp[u][2]);
}
dp[p][0] = 1 + s4;
dp[p][1] = s3 + sub03;
dp[p][2] = s2 + sub12;
dp[p][3] = s2;
dp[p][4] = s3;
// dp to S
dp[p][2] = min(min(dp[p][0], dp[p][1]), dp[p][2]);
dp[p][3] = min(dp[p][2], dp[p][3]);
dp[p][4] = min(dp[p][3], dp[p][4]);
return ;
}
int main() {
memset(head, -1, sizeof head);
int n;
scanf("%d", &n);
for(int i = 2; i <= n; i++) {
int tmp;
scanf("%d", &tmp);
add(i, tmp);
add(tmp, i);
}
dfs(1, -1);
printf("%d\n", dp[1][2]);
}
贪心:
更简单、更有效、使用范围更广
对于当前还没有被遍历的点中的最低点 ,在 的眼中其他的点只有: 的父亲, 的爷爷, 的兄弟。
显然选择 的爷爷最优,不仅解决了这里的所有点,还可以再往上延两个点。
现在有两个问题:最低点的寻找、更新的维护。
尝试使用数组按照深度顺序存储,从前往后线性遍历。就解决了最低点的问题。
至于更新点的维护,开一个dis数组,代表某个点的子树中,前dis层被覆盖。对于 点,标记其爷爷后,dis[爷爷]=2,dis[父亲]=1,dis[曾爷爷]=1,dis[曾曾爷爷]=0。
那么对于遍历其他点的时候,看看它父亲和爷爷的dis值即可判断是否被覆盖。
#include<bits/stdc++.h>
using namespace std;
const int N = 1005, M = 2005;
int head[N], nex[M], to[M], now;
void add(int a, int b) {
nex[++now] = head[a];
head[a] = now;
to[now] = b;
}
int fa[N], deep[N], dis[N];
void dfs(int p, int f) {
for(int i = head[p]; ~i; i = nex[i]) {
int u = to[i];
if(u == f)
continue;
fa[u] = p;
deep[u] = deep[p] + 1;
dfs(u, p);
}
return ;
}
int tmp[N];
bool cmp(const int a, const int b) {
return deep[a] > deep[b];
}
int main() {
memset(head, -1, sizeof head);
memset(dis, -1, sizeof(dis));
int n;
scanf("%d", &n);
for(int i = 2; i <= n; i++) {
int tmp;
scanf("%d", &tmp);
add(i, tmp);
add(tmp, i);
}
// 考虑超过root的情况
fa[1] = 0;
fa[0] = 0;
deep[1] = 1;
dfs(1, -1);
for(int i = 1; i <= n; i++)
tmp[i] = i;
sort(tmp + 1, tmp + 1 + n, cmp);
int ans = 0;
for(int i = 1; i <= n; i++) {
int p = tmp[i], f = fa[p], ff = fa[f], fff = fa[ff], ffff = fa[fff];
if(dis[p] >= 0 || dis[f] >= 1 || dis[ff] >= 2)
continue;
ans++;
dis[f] = max(dis[f], 1);
dis[ff] = max(dis[ff], 2);
dis[fff] = max(dis[fff], 1);
dis[ffff] = max(dis[ffff], 0);
}
printf("%d\n", ans);
}