文章目录
- 题目
- 法一:树上DP
- 思路
- 代码实现
- 法二:贪心 + 搜索
- 思路
- 代码实现
题目
2020年,人类在火星上建立了一个庞大的基地群,总共有n个基地。起初为了节约材料,人类只修建了n-1条道路来连接这些基地,并且每两个基地都能够通过道路到达,所以所有的基地形成了一个巨大的树状结构。如果基地A到基地B至少要经过d条道路的话,我们称基地A到基地B的距离为d。
由于火星上非常干燥,经常引发火灾,人类决定在火星上修建若干个消防局。消防局只能修建在基地里,每个消防局有能力扑灭与它距离不超过2的基地的火灾。
你的任务是计算至少要修建多少个消防局才能够确保火星上所有的基地在发生火灾时,消防队有能力及时扑灭火灾。
输入格式
输入文件的第一行为n (n<=1000),表示火星上基地的数目。接下来的n-1行每行有一个正整数,其中文件第i行的正整数为a[i],表示从编号为i的基地到编号为a[i]的基地之间有一条道路,为了更加简洁的描述树状结构的基地群,有a[i]<i。
输出格式
输出文件仅有一个正整数,表示至少要设立多少个消防局才有能力及时扑灭任何基地发生的火灾。
输入输出样例
输入
6
1
2
3
4
5
输出
2
法一:树上DP
思路
这个可是重头戏,让我抓狂了整整一晚呢!!!
先给出DP[i][j]DP[i][j]DP[i][j]的含义:
表示距离树上的i点最近的一个消防站的距离为j的所建的消防站的个数
那么我们就可以get到以下DP含义(下面要用,一定要理解哦~~)
DP[i][0]:把离i最近的消防站就直接设在i节点上DP[i][0]:把离i最近的消防站就直接设在i节点上DP[i][0]:把离i最近的消防站就直接设在i节点上
DP[i][1]:把离i最近的消防站设在i的某一个儿子节点vDP[i][1]:把离i最近的消防站设在i的某一个儿子节点vDP[i][1]:把离i最近的消防站设在i的某一个儿子节点v
DP[i][2]:把消防站设在i的某一个孙子节点vDP[i][2]:把消防站设在i的某一个孙子节点vDP[i][2]:把消防站设在i的某一个孙子节点v
DP[i][3]:把消防站设在i的某一个儿子节点v的某一个孙子节点DP[i][3]:把消防站设在i的某一个儿子节点v的某一个孙子节点DP[i][3]:把消防站设在i的某一个儿子节点v的某一个孙子节点
DP[i][4]:把消防站设在i的某一个孙子节点v的某一个孙子节点DP[i][4]:把消防站设在i的某一个孙子节点v的某一个孙子节点DP[i][4]:把消防站设在i的某一个孙子节点v的某一个孙子节点
接下来还应该明白这样一个DP式,假设v是u的一个儿子节点
DP[u][i]=DP[v][i−1]DP[u][i]=DP[v][i-1]DP[u][i]=DP[v][i−1]
解释:距离u的距离为i,那么距离u的儿子v的距离就-1了
接下来就要推DP式了,上图分析理解
(虽然给的是很简单的单链,必要时要幻想每一个v都有兄弟,他们并不孤单)
我们就只站在root往深处儿子看,不用管祖先,
因为这些都会在回溯的时候在祖先那一层进行处理
①:把消防站建在root处
看图可以知道,v1,v2两层会被root覆盖,
这个时候我们就要对于root的每一个子树内部进行处理,
因为其它子树再往深了建就更不可能覆盖到其他子树的点了,
所以DP[i][0]+=DP[v][4]DP[i][0]+=DP[v][4]DP[i][0]+=DP[v][4]
解释:看图?v3, v4,v5…等深的节点都没有被覆盖,这里就要用到贪心的思想
我把消防站建在v5肯定要优于v3,v4因为v5可以再往下多覆盖两层
其实应该加上DP[i][5]DP[i][5]DP[i][5]因为v5距离root的距离是5,
上面就已经铺垫了DP[i][5]=DP[v][4]DP[i][5]=DP[v][4]DP[i][5]=DP[v][4]
②:把消防站建在v3处
那么v3可以覆盖v1,v2,v4,v5
所以对于DP[i][3]+=DP[v][2]DP[i][3]+=DP[v][2]DP[i][3]+=DP[v][2]
可是root没有被覆盖啊???
不急,这个root不是非要现在处理不行,
root可以被丢给它的爷爷覆盖,等到回溯的时候,它就变成了爷爷的v2,也会被覆盖
③:把消防站建在v4处
那么与②差不多
DP[i][4]+=DP[v][3]DP[i][4]+=DP[v][3]DP[i][4]+=DP[v][3]
我们把root和root的儿子v1那一层都丢给root的父亲去覆盖,
接下来的两种情况就稍微有点复杂了,
④:把消防站建在v1处
可以知道v1可以通过V形蛇皮操作覆盖了root的其它儿子以及root
那么对于其它的子树而言,应该建在它们的v4那一层,
因为它们没有必要去覆盖v1那一层的所有节点
所以我们要找到最小的一个v1节点,然后对于root的其它子节点找v4那一层
⑤:把消防站建在v2处
与④雷同的
可以知道v2可以通过I形蛇皮操作覆盖了root以及v2那一条单链的v1节点
那么对于其它的子树而言,应该建在它们的v3那一层,
因为它们必须要内部消化去覆盖它们的v1节点,就不用管root
所以我们要找到最小的一个v2节点,然后对于root的其它子节点找v5那一层
对于④⑤的DP处理,在这里讲一下,分析出来要求最小值
但是其实没有必要去双重循环找,因为我们发现就是求一个
④:DP[v][0]DP[v][0]DP[v][0]和其他所有DP[v′][3]DP[v'][3]DP[v′][3]的最小值
⑤:DP[v][1]DP[v][1]DP[v][1]和其他所有DP[v′][2]DP[v'][2]DP[v′][2]的最小值
而DP[v][0]DP[v][0]DP[v][0]肯定大于DP[v′][3]DP[v'][3]DP[v′][3],DP[v][1]DP[v][1]DP[v][1]肯定大于DP[v′][2]DP[v'][2]DP[v′][2]
所以就是求一个DP[v][0]DP[v][0]DP[v][0]和DP[v][3]DP[v][3]DP[v][3]的最小值,DP[v][1]DP[v][1]DP[v][1]和DP[v][2]DP[v][2]DP[v][2]的最小值
用O(n)O(n)O(n)就可以进行更新
代码实现
#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
#define MAXN 1005
vector < int > G[MAXN];
int n, result = 0x7f7f7f7f;
int f[MAXN];
int dp[MAXN][5];void dfs ( int u ) {dp[u][0] = 1;for ( int i = 0;i < G[u].size();i ++ ) {int v = G[u][i];if ( v == f[u] ) continue;dfs ( v );dp[u][0] += dp[v][4];dp[u][3] += dp[v][2];dp[u][4] += dp[v][3];}if ( G[u].size() == 1 && u != 1 )dp[u][1] = dp[u][2] = 1;else {int f1 = 0x7f7f7f7f, f2 = 0x7f7f7f7f;for ( int i = 0;i < G[u].size();i ++ ) {int v = G[u][i];if ( v == f[u] ) continue;f1 = min ( f1, dp[v][0] - dp[v][3] );f2 = min ( f2, dp[v][1] - dp[v][2] );dp[u][1] += dp[v][3];dp[u][2] += dp[v][2];}dp[u][1] += f1;dp[u][2] += f2;}for ( int i = 1;i <= 4;i ++ )dp[u][i] = min ( dp[u][i], dp[u][i - 1] );
}int main () {scanf ( "%d", &n );for ( int i = 2;i <= n;i ++ ) {scanf ( "%d", &f[i] );G[f[i]].push_back( i );G[i].push_back( f[i] );}dfs ( 1 );printf ( "%d", dp[1][2] );return 0;
}
法二:贪心 + 搜索
思路
我写这篇文章的重点就是解释法一,所以这个贪心我就不会像以前一样,细讲了
反正也很简单,连我这种蒟蒻都一次
首先我们考虑一条单链的状态,那么我们肯定都想把消防站建在中间,这样它就可以往上灭两层再往下灭两层
这样肯定是最优的
在这里贪心思想就出现了,我们找到一个当前深度最深的点,
去找这个点的祖先的祖先–他的爷爷fa
这样的话,fa不仅能把当前点覆盖,还可以覆盖fa的所有儿子,以至于fa的所有兄弟,
甚至于fa的父亲和爷爷
至于怎么覆盖这些点,我们可以用bfs以fa为中心去扩散半径为2的所有点
不用担心会很多,可以证明每个点最多被访问3次
一次是它作为中心,去扩散
一次是它作为中转点,即半径为1再去扩散一层
一次是它作为边缘点,即半径为2停止扩散
是不是这个贪心很简单
代码实现
#include <queue>
#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
#define MAXN 1005
struct node {int u, step;node () {}node ( int U, int STEP ) {u = U;step = STEP;}
};
struct noded {int dep, id;
}tree[MAXN];
queue < node > q;
vector < int > G[MAXN];
int n, result;
int f[MAXN];
bool vis[MAXN];bool cmp ( noded x, noded y ) {return x.dep > y.dep;
}void dfs ( int u, int depth ) {tree[u].dep = depth;tree[u].id = u;for ( int i = 0;i < G[u].size();i ++ ) {int v = G[u][i];if ( v == f[u] ) continue;dfs ( v, depth + 1 );}
}void bfs ( int root ) {vis[root] = 1;q.push ( node ( root, 0 ) );while ( ! q.empty() ) {node t = q.front();q.pop();if ( t.step == 2 ) continue;for ( int i = 0;i < G[t.u].size();i ++ ) {int v = G[t.u][i];if ( ! vis[v] )vis[v] = 1;q.push( node ( v, t.step + 1 ) );}}
}void solve () {for ( int i = 1;i <= n;i ++ ) {if ( ! vis[tree[i].id] ) {result ++;int fa = f[f[tree[i].id]];if ( fa == 0 ) fa = 1;bfs ( fa );}}
}int main() {scanf ( "%d", &n );for ( int i = 2;i <= n;i ++ ) {scanf ( "%d", &f[i] );G[f[i]].push_back( i );G[i].push_back( f[i] );}dfs ( 1, 1 );sort ( tree + 1, tree + n + 1, cmp );solve ();printf ( "%d", result );return 0;
}