日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

[Luogu2279][HNOI2003] 消防局的设立

發布時間:2023/12/3 编程问答 34 豆豆
生活随笔 收集整理的這篇文章主要介紹了 [Luogu2279][HNOI2003] 消防局的设立 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

文章目錄

    • 題目
  • 法一:樹上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]ii
DP[i][1]:把離i最近的消防站設在i的某一個兒子節點vDP[i][1]:把離i最近的消防站設在i的某一個兒子節點vDP[i][1]iiv
DP[i][2]:把消防站設在i的某一個孫子節點vDP[i][2]:把消防站設在i的某一個孫子節點vDP[i][2]iv
DP[i][3]:把消防站設在i的某一個兒子節點v的某一個孫子節點DP[i][3]:把消防站設在i的某一個兒子節點v的某一個孫子節點DP[i][3]iv
DP[i][4]:把消防站設在i的某一個孫子節點v的某一個孫子節點DP[i][4]:把消防站設在i的某一個孫子節點v的某一個孫子節點DP[i][4]iv

接下來還應該明白這樣一個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; }

總結

以上是生活随笔為你收集整理的[Luogu2279][HNOI2003] 消防局的设立的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。