P2279 消防局的设立 (树形DP or 贪心)
Posted winter-bamboo
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了P2279 消防局的设立 (树形DP or 贪心)相关的知识,希望对你有一定的参考价值。
树形DP写法
看到这个题的要求,很容易相到这是一个树形DP的问题,但是dp数组应该如何设计并转移才是关键
dp[i][0]代表当前结点可以向上覆盖2层,自身一定被覆盖
dp[i][1]代表当前结点可以向上覆盖1层,自身一定被覆盖
dp[i][2]代表当前结点可以向上覆盖0层,自身一定被覆盖
dp[i][3]代表当前结点可以向下覆盖1层,表示自己不一定被覆盖,但是儿子一定全部被覆盖
dp[i][4]代表当前结点可以向下覆盖2层,表示自己不一定被覆盖,但是孙子一定全部被覆盖
所谓向上覆盖x层,即当前结点向上x个结点(祖先结点)作为根结点的树被完全覆盖的情况下,最少需要设立的消防站数目
所谓向下覆盖x层,即当前结点向下x个结点(后代结点)作为根结点的树被完全覆盖的情况下,最少需要设立的消防站数目
显然满足
dp[i][0] >= dp[i][1] >= dp[i][2] >= dp[i][3] >= dp[i][4]
dp[u][0] = 1 + min(dp[v][0~4])
如果当前结点想要覆盖向上2层,则自身必然安置一个消防站,而u所有子节点就随意了
dp[u][1] = min{ dp[v][0] + ∑dp[e][0~3] , dp[u][0] }
覆盖到当前结点上1层,有两种情况:
1)在其子节点安置了至少一个消防站,这样一来,不仅覆盖了当前结点上一层的结点,而且
安置了消防站的子节点将覆盖他的兄弟结点,所以其兄弟结点至少保证自身子节点被完全覆盖即可
2)当前结点安置消防站,覆盖了上两层的同时,也可以覆盖当前结点上一层
dp[u][2] = min{dp[v][1] + ∑dp[e][2],dp[u][1],dp[u][0]}
覆盖到当前结点上0层,分三种情况
1)刚好覆盖到当前结点,则至少选择一个当前结点的孙子结点,由于这个消防站无法覆盖到当前结点的其他子节点,
那么其余结点至少保证自身被覆盖即可
2)可以覆盖到当前结点向上一层,同时覆盖了当前结点的子孙结点,此时即在当前结点的子节点设立消防站
3)可以覆盖到当前结点向上二层,同时覆盖了当前结点的子孙结点,此时即在当前结点设立消防站
dp[u][3] = ∑dp[v][2]
覆盖当前结点所有的子节点,则保证当前结点的所有子节点被覆盖
dp[u][4] = ∑dp[v][3]
覆盖当前结点所有的孙子节点,则保证当前结点的所有子节点的子节点被覆盖
最后,为了覆盖整棵树,我们输出dp[1]0]即可
代码区
#include<iostream> #include<cstdio> #include<algorithm> #include<cstring> #include<queue> #include<string> #include<fstream> #include<vector> #include<stack> #include <map> #include <iomanip> #define bug cout << "**********" << endl #define show(x, y) cout<<"["<<x<<","<<y<<"] " #define LOCAL = 1; using namespace std; typedef long long ll; const int inf = 1e9 + 7; const int mod = 1e9 + 7; const int Max = 1e3 + 10; int n, m, k; int head[Max], tot; int to[Max], Next[Max]; int dp[Max][5]; /* * dp[i][0]代表当前结点可以向上覆盖2层,自身一定被覆盖 * dp[i][1]代表当前结点可以向上覆盖1层,自身一定被覆盖 * dp[i][2]代表当前结点可以向上覆盖0层,自身一定被覆盖 * dp[i][3]代表当前结点可以向下覆盖1层,表示自己不一定被覆盖,但是儿子一定全部被覆盖 * dp[i][4]代表当前结点可以向下覆盖2层,表示自己不一定被覆盖,但是孙子一定全部被覆盖 * * 所谓向上覆盖x层,即当前结点向上x个结点(祖先结点)作为根结点的树被完全覆盖的情况下,最少需要设立的消防站数目 * 所谓向下覆盖x层,即当前结点向下x个结点(后代结点)作为根结点的树被完全覆盖的情况下,最少需要设立的消防站数目 * * 显然满足 * dp[i][0] >= dp[i][1] >= dp[i][2] >= dp[i][3] >= dp[i][4] * * dp[u][0] = 1 + min(dp[v][0~4]) * 如果当前结点想要覆盖向上2层,则自身必然安置一个消防站,而u所有子节点就随意了 * * dp[u][1] = min{ dp[v][0] + ∑dp[e][0~3] , dp[u][0] } * 覆盖到当前结点上1层,有两种情况: * 1)在其子节点安置了至少一个消防站,这样一来,不仅覆盖了当前结点上一层的结点,而且 * 安置了消防站的子节点将覆盖他的兄弟结点,所以其兄弟结点至少保证自身子节点被完全覆盖即可 * 2)当前结点安置消防站,覆盖了上两层的同时,也可以覆盖当前结点上一层 * * dp[u][2] = min{dp[v][1] + ∑dp[e][2],dp[u][1],dp[u][0]} * 覆盖到当前结点上0层,分三种情况 * 1)刚好覆盖到当前结点,则至少选择一个当前结点的孙子结点,由于这个消防站无法覆盖到当前结点的其他子节点, * 那么其余结点至少保证自身被覆盖即可 * 2)可以覆盖到当前结点向上一层,同时覆盖了当前结点的子孙结点,此时即在当前结点的子节点设立消防站 * 3)可以覆盖到当前结点向上二层,同时覆盖了当前结点的子孙结点,此时即在当前结点设立消防站 * * dp[u][3] = ∑dp[v][2] * 覆盖当前结点所有的子节点,则保证当前结点的所有子节点被覆盖 * * dp[u][4] = ∑dp[v][3] * 覆盖当前结点所有的孙子节点,则保证当前结点的所有子节点的子节点被覆盖 * * 最后,为了覆盖整棵树,我们输出dp[1]0]即可 */ void add(int u, int v) { to[tot] = v; Next[tot] = head[u]; head[u] = tot++; } void dfs(int u) { dp[u][0] = 1; dp[u][1] = inf; dp[u][2] = inf; dp[u][3] = 0; dp[u][4] = 0; for (int i = head[u]; i != -1; i = Next[i]) { int v = to[i]; dfs(v); dp[u][0] += dp[v][4]; dp[u][3] += dp[v][2]; dp[u][4] += dp[v][3]; } if (head[u] == -1) //没有子节点了,此时dp[u][0~2]必须使得u安置消防站 { dp[u][1] = dp[u][2] = 1; return; } for (int i = head[u]; i != -1; i = Next[i]) { int v = to[i]; int sum1 = 0, sum2 = 0; //记录∑dp[e][3]和∑dp[e][4] for (int j = head[u]; j != -1; j = Next[j]) { int e = to[j]; if (e == v) continue; sum1 += dp[e][3]; sum2 += dp[e][2]; } dp[u][1] = min(dp[u][1], dp[v][0] + sum1); dp[u][2] = min(dp[u][2], dp[v][1] + sum2); } for (int i = 1; i <= 4; i++) //最后综合处理一下 dp[u][i] = min(dp[u][i], dp[u][i - 1]); } int main() { #ifdef LOCAL // freopen("input.txt", "r", stdin); // freopen("output.txt", "w", stdout); #endif memset(head, -1, sizeof(head)); tot = 0; scanf("%d", &n); for (int v = 2, u; v <= n; v++) scanf("%d", &u), add(u, v); dfs(1); printf("%d ", dp[1][2]); return 0; }
贪心写法(适用于在树种,求点覆盖半径为k的最小点覆盖)
其实这种解法对于这一类型的题目非常的适合,主要体现在不需要像树形DP一样确定状态并且易于理解,这种解法的思想如下:
我们用dis[i]表示结点i到最近的消防站的最短距离,如果dis[i] > k ,说明结点i不在已存在的消防站的覆盖范围内,为了保证消防站利用率最大化,我们在结点i向上第k个结点,记作x,也就是结点i覆盖的极限范围出处设立消防站,这样一来不仅使得结点i被覆盖,还可以覆盖更多的结点,然后我们更新x向上k层范围内所有结点的dis,即更新各点到消防站的最近距离,重复这一过程,统计消防站的数目即可
代码区
#include<iostream> #include<cstdio> #include<algorithm> #include<cstring> #include<queue> #include<string> #include<fstream> #include<vector> #include<stack> #include <map> #include <iomanip> #define bug cout << "**********" << endl #define show(x, y) cout<<"["<<x<<","<<y<<"] " #define LOCAL = 1; using namespace std; typedef long long ll; const int inf = 1e9 + 7; const int mod = 1e9 + 7; const int Max = 1e3 + 10; struct Node { int depth; //记录当前结点深度 int id; //记录结点编号 } node[Max]; int n, k; int dis[Max]; //dis[i]记录结点i到最近的消防站的最近距离 int fa[Max]; //dp[i]记录i的父节点 bool cmp(Node node1, Node node2) { return node1.depth > node2.depth; } int main() { #ifdef LOCAL // freopen("input.txt", "r", stdin); // freopen("output.txt", "w", stdout); #endif scanf("%d", &n); k = 2; //结点覆盖半径为2,根据实际情况改变 node[1].depth = 0; node[1].id = 1; dis[0] = dis[1] = inf; //处理根结点(这里灵活建图即可) for (int v = 2, u; v <= n; v++) { scanf("%d", &u); //构建的边为 u --> v node[v].depth = node[u].depth + 1; node[v].id = v; fa[v] = u; dis[v] = inf; } sort(node + 1, node + 1 + n, cmp); int sum = 0; for (int i = 1; i <= n; i++) { int son = node[i].id; int now = node[i].id; //当前结点 for (int j = 1; j <= k; j++) //处理出k个祖先结点到当前结点的最佳距离 { now = fa[now]; dis[son] = min(dis[son], dis[now] + j); } if (dis[son] > k) //代表每个结点覆盖半径k { dis[now] = 0; //此处总是在最远祖先处设立消防站 sum++; for (int j = 1; j <= k; j++) //之后由最远祖先结点向上k层更新其余点的距离 { now = fa[now]; dis[now] = min(dis[now], j); } } } printf("%d ", sum); return 0; }
以上是关于P2279 消防局的设立 (树形DP or 贪心)的主要内容,如果未能解决你的问题,请参考以下文章