[Luogu2279][HNOI2003] 消防局的设立
文章目錄
- 題目
- 法一:樹上DP
- 思路
- 代碼實(shí)現(xiàn)
- 法二:貪心 + 搜索
- 思路
- 代碼實(shí)現(xiàn)
題目
2020年,人類在火星上建立了一個(gè)龐大的基地群,總共有n個(gè)基地。起初為了節(jié)約材料,人類只修建了n-1條道路來連接這些基地,并且每兩個(gè)基地都能夠通過道路到達(dá),所以所有的基地形成了一個(gè)巨大的樹狀結(jié)構(gòu)。如果基地A到基地B至少要經(jīng)過d條道路的話,我們稱基地A到基地B的距離為d。
由于火星上非常干燥,經(jīng)常引發(fā)火災(zāi),人類決定在火星上修建若干個(gè)消防局。消防局只能修建在基地里,每個(gè)消防局有能力撲滅與它距離不超過2的基地的火災(zāi)。
你的任務(wù)是計(jì)算至少要修建多少個(gè)消防局才能夠確保火星上所有的基地在發(fā)生火災(zāi)時(shí),消防隊(duì)有能力及時(shí)撲滅火災(zāi)。
輸入格式
輸入文件的第一行為n (n<=1000),表示火星上基地的數(shù)目。接下來的n-1行每行有一個(gè)正整數(shù),其中文件第i行的正整數(shù)為a[i],表示從編號(hào)為i的基地到編號(hào)為a[i]的基地之間有一條道路,為了更加簡(jiǎn)潔的描述樹狀結(jié)構(gòu)的基地群,有a[i]<i。
輸出格式
輸出文件僅有一個(gè)正整數(shù),表示至少要設(shè)立多少個(gè)消防局才有能力及時(shí)撲滅任何基地發(fā)生的火災(zāi)。
輸入輸出樣例
輸入
6
1
2
3
4
5
輸出
2
法一:樹上DP
思路
這個(gè)可是重頭戲,讓我抓狂了整整一晚呢!!!
先給出DP[i][j]DP[i][j]DP[i][j]的含義:
表示距離樹上的i點(diǎn)最近的一個(gè)消防站的距離為j的所建的消防站的個(gè)數(shù)
那么我們就可以get到以下DP含義(下面要用,一定要理解哦~~)
DP[i][0]:把離i最近的消防站就直接設(shè)在i節(jié)點(diǎn)上DP[i][0]:把離i最近的消防站就直接設(shè)在i節(jié)點(diǎn)上DP[i][0]:把離i最近的消防站就直接設(shè)在i節(jié)點(diǎn)上
DP[i][1]:把離i最近的消防站設(shè)在i的某一個(gè)兒子節(jié)點(diǎn)vDP[i][1]:把離i最近的消防站設(shè)在i的某一個(gè)兒子節(jié)點(diǎn)vDP[i][1]:把離i最近的消防站設(shè)在i的某一個(gè)兒子節(jié)點(diǎn)v
DP[i][2]:把消防站設(shè)在i的某一個(gè)孫子節(jié)點(diǎn)vDP[i][2]:把消防站設(shè)在i的某一個(gè)孫子節(jié)點(diǎn)vDP[i][2]:把消防站設(shè)在i的某一個(gè)孫子節(jié)點(diǎn)v
DP[i][3]:把消防站設(shè)在i的某一個(gè)兒子節(jié)點(diǎn)v的某一個(gè)孫子節(jié)點(diǎn)DP[i][3]:把消防站設(shè)在i的某一個(gè)兒子節(jié)點(diǎn)v的某一個(gè)孫子節(jié)點(diǎn)DP[i][3]:把消防站設(shè)在i的某一個(gè)兒子節(jié)點(diǎn)v的某一個(gè)孫子節(jié)點(diǎn)
DP[i][4]:把消防站設(shè)在i的某一個(gè)孫子節(jié)點(diǎn)v的某一個(gè)孫子節(jié)點(diǎn)DP[i][4]:把消防站設(shè)在i的某一個(gè)孫子節(jié)點(diǎn)v的某一個(gè)孫子節(jié)點(diǎn)DP[i][4]:把消防站設(shè)在i的某一個(gè)孫子節(jié)點(diǎn)v的某一個(gè)孫子節(jié)點(diǎn)
接下來還應(yīng)該明白這樣一個(gè)DP式,假設(shè)v是u的一個(gè)兒子節(jié)點(diǎn)
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式了,上圖分析理解
(雖然給的是很簡(jiǎn)單的單鏈,必要時(shí)要幻想每一個(gè)v都有兄弟,他們并不孤單)
我們就只站在root往深處兒子看,不用管祖先,
因?yàn)檫@些都會(huì)在回溯的時(shí)候在祖先那一層進(jìn)行處理
①:把消防站建在root處
看圖可以知道,v1,v2兩層會(huì)被root覆蓋,
這個(gè)時(shí)候我們就要對(duì)于root的每一個(gè)子樹內(nèi)部進(jìn)行處理,
因?yàn)槠渌訕湓偻盍私ň透豢赡芨采w到其他子樹的點(diǎn)了,
所以DP[i][0]+=DP[v][4]DP[i][0]+=DP[v][4]DP[i][0]+=DP[v][4]
解釋:看圖?v3, v4,v5…等深的節(jié)點(diǎn)都沒有被覆蓋,這里就要用到貪心的思想
我把消防站建在v5肯定要優(yōu)于v3,v4因?yàn)関5可以再往下多覆蓋兩層
其實(shí)應(yīng)該加上DP[i][5]DP[i][5]DP[i][5]因?yàn)関5距離root的距離是5,
上面就已經(jīng)鋪墊了DP[i][5]=DP[v][4]DP[i][5]=DP[v][4]DP[i][5]=DP[v][4]
②:把消防站建在v3處
那么v3可以覆蓋v1,v2,v4,v5
所以對(duì)于DP[i][3]+=DP[v][2]DP[i][3]+=DP[v][2]DP[i][3]+=DP[v][2]
可是root沒有被覆蓋啊???
不急,這個(gè)root不是非要現(xiàn)在處理不行,
root可以被丟給它的爺爺覆蓋,等到回溯的時(shí)候,它就變成了爺爺?shù)膙2,也會(huì)被覆蓋
③:把消防站建在v4處
那么與②差不多
DP[i][4]+=DP[v][3]DP[i][4]+=DP[v][3]DP[i][4]+=DP[v][3]
我們把root和root的兒子v1那一層都丟給root的父親去覆蓋,
接下來的兩種情況就稍微有點(diǎn)復(fù)雜了,
④:把消防站建在v1處
可以知道v1可以通過V形蛇皮操作覆蓋了root的其它兒子以及root
那么對(duì)于其它的子樹而言,應(yīng)該建在它們的v4那一層,
因?yàn)樗鼈儧]有必要去覆蓋v1那一層的所有節(jié)點(diǎn)
所以我們要找到最小的一個(gè)v1節(jié)點(diǎn),然后對(duì)于root的其它子節(jié)點(diǎn)找v4那一層
⑤:把消防站建在v2處
與④雷同的
可以知道v2可以通過I形蛇皮操作覆蓋了root以及v2那一條單鏈的v1節(jié)點(diǎn)
那么對(duì)于其它的子樹而言,應(yīng)該建在它們的v3那一層,
因?yàn)樗鼈儽仨氁獌?nèi)部消化去覆蓋它們的v1節(jié)點(diǎn),就不用管root
所以我們要找到最小的一個(gè)v2節(jié)點(diǎn),然后對(duì)于root的其它子節(jié)點(diǎn)找v5那一層
對(duì)于④⑤的DP處理,在這里講一下,分析出來要求最小值
但是其實(shí)沒有必要去雙重循環(huán)找,因?yàn)槲覀儼l(fā)現(xiàn)就是求一個(gè)
④: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]
所以就是求一個(gè)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)就可以進(jìn)行更新
代碼實(shí)現(xià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; }法二:貪心 + 搜索
思路
我寫這篇文章的重點(diǎn)就是解釋法一,所以這個(gè)貪心我就不會(huì)像以前一樣,細(xì)講了
反正也很簡(jiǎn)單,連我這種蒟蒻都一次
首先我們考慮一條單鏈的狀態(tài),那么我們肯定都想把消防站建在中間,這樣它就可以往上滅兩層再往下滅兩層
這樣肯定是最優(yōu)的
在這里貪心思想就出現(xiàn)了,我們找到一個(gè)當(dāng)前深度最深的點(diǎn),
去找這個(gè)點(diǎn)的祖先的祖先–他的爺爺fa
這樣的話,fa不僅能把當(dāng)前點(diǎn)覆蓋,還可以覆蓋fa的所有兒子,以至于fa的所有兄弟,
甚至于fa的父親和爺爺
至于怎么覆蓋這些點(diǎn),我們可以用bfs以fa為中心去擴(kuò)散半徑為2的所有點(diǎn)
不用擔(dān)心會(huì)很多,可以證明每個(gè)點(diǎn)最多被訪問3次
一次是它作為中心,去擴(kuò)散
一次是它作為中轉(zhuǎn)點(diǎn),即半徑為1再去擴(kuò)散一層
一次是它作為邊緣點(diǎn),即半徑為2停止擴(kuò)散
是不是這個(gè)貪心很簡(jiǎn)單
代碼實(shí)現(xiàn)
#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; }總結(jié)
以上是生活随笔為你收集整理的[Luogu2279][HNOI2003] 消防局的设立的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: [2019CSP多校联赛普及组第五周]
- 下一篇: 小奇探险