The Solution of P2279
本题先前的题解,可以说是没有一篇做到了内容质量与美观程度并存,而这又是一道经典题目,所以打算写一篇完整而又详细美观的题解。
本文含有贪心及 dp 的做法,适合所有语言人群阅读。
0x01 解法
解法 A. 贪心
A-1 贪心思路
我们贪心地先选择深度大的节点,由于一个消防站能够覆盖五层的节点,我们找出当前选择节点
开三个 bool 数组
每次遍历时只要寻找一下
以下是不在
(上述情况的证明非常容易,留作习题。)
这里再记
若不满足上面提及的情况,我们就可以对
不管
还不明白?那就看看下面的图理解。
A-2 贪心实例
考虑如下的一棵树。
其中
我们先按
经过对爷爷
接下来依次扫描点
当扫描到点
答案为
A-3 贪心代码
#include <cstdio>
#include <algorithm>
#define N 2021
using namespace std;
bool diz[N], dio[N], dit[N];
int fa[N], de[N], ind[N];
bool cmp(int a, int b){
return de[a] > de[b];
}
bool check(int u, int op){
if(op == 0) return (!diz[u]) && (!dio[u]) && (!dit[u]);
if(op == 1) return (!diz[u]) && (!dio[u]);
return !diz[u];
}
int n;
int ans = 0;
int main(){
scanf("%d", &n);
ind[1] = 1;
for(int i = 2 ; i <= n ; i++){
scanf("%d", &fa[i]);
de[i] = de[fa[i]] + 1;
ind[i] = i;
}
sort(ind + 1, ind + n + 1, cmp);
for(int i = 1 ; i <= n ; i++){
int u = ind[i], v = fa[u], w = fa[v], x = fa[w], y = fa[x];
if(check(u, 0) && check(v, 1) && check(w, 2))
ans++, diz[w] = 1, dio[x] = 1, dit[y] = 1;
}
printf("%d\n", ans);
return 0;
}
解法 B. dp
B-2 dp 状态设计
令
注意到对于
B-2 dp 转移方程
容易发现,若
求
- 若覆盖恰好
1 层,则i 的儿子中必有一个点u 经过了染色,其他儿子v 都覆盖了至少-1 层。 - 若覆盖
2 层,答案就是dp_{i,2} 。
于是就有式子
求
- 恰好覆盖
0 层,则i 的儿子中至少有一个点u 能覆盖1 层,其他儿子v 都覆盖了至少0 层。 - 若覆盖
1,2 层,则答案就是\min(dp_{i,1},dp_{i,2}) 。
于是就有式子:
- 对于
dp_{i,-1} 的求解,我们只要让其所有儿子都被覆盖即可。 - 对于
dp_{i,-2} 的求解,我们只要让其所有孙子都被覆盖即可。
于是有式子:
我们得到了上述 5 个转移方程后,即可开始转移。
最后的答案是覆盖节点
为了方便,我们在开数组的时候,将第二维统一加上
B-3 dp 代码
#include <cstdio>
#include <vector>
#include <algorithm>
#define N 2021
#define INF 99999999
using namespace std;
vector <int> t[N];
int dp[N][5];
int n;
void dfs(int u){
dp[u][4] = 1, dp[u][1] = 0, dp[u][0] = 0;
int len = t[u].size();
for(int i = 0 ; i < len ; i++){
int v = t[u][i];
dfs(v);
dp[u][4] += dp[v][0];
dp[u][1] += dp[v][2];
dp[u][0] += dp[v][1];
}
if(!len) dp[u][2] = dp[u][3] = 1;
else{
dp[u][2] = dp[u][3] = INF;
int cnt1 = 0, cnt2 = 0;
// %
for(int i = 0 ; i < len ; i++){
int v = t[u][i];
cnt1 += dp[v][1];
cnt2 += dp[v][2];
}
for(int i = 0 ; i < len ; i++){
int v = t[u][i];
dp[u][3] = min(cnt1 - dp[v][1] + dp[v][4], dp[u][3]);
dp[u][2] = min(cnt2 - dp[v][2] + dp[v][3], dp[u][2]);
}
// %
}
for(int i = 3 ; i >= 0 ; i--)
dp[u][i] = min(dp[u][i + 1], dp[u][i]);
}
int main(){
scanf("%d", &n);
for(int i = 2 ; i <= n ; i++){
int u;
scanf("%d", &u);
t[u].push_back(i);
}
dfs(1);
printf("%d\n", dp[1][2]);
return 0;
}
这里计算
0x02 后记
比较
实测表明 dp 耗时 33 ms,贪心耗时 30 ms。
其中,dp 码量更大但转移较为显然;贪心码量小但理解需要再花一点功夫。
修改日志
- 2023.07.24 重构全文,改正多处用词,修了一车笔误,重新绘制了插图。改写前版本备份:https://www.luogu.com.cn/paste/80vc1ome。