P2279 [HNOI2003]消防局的設立
題目描述
2020年,人類在火星上建立了一個龐大的基地群,總共有n個基地。起初為了節約材料,人類只修建了n-1條道路來連接這些基地,並且每兩個基地都能夠通過道路到達,所以所有的基地形成了一個巨大的樹狀結構。如果基地A到基地B至少要經過d條道路的話,我們稱基地A到基地B的距離為d。
由於火星上非常幹燥,經常引發火災,人類決定在火星上修建若幹個消防局。消防局只能修建在基地裏,每個消防局有能力撲滅與它距離不超過2的基地的火災。
你的任務是計算至少要修建多少個消防局才能夠確保火星上所有的基地在發生火災時,消防隊有能力及時撲滅火災。
輸入輸出格式
輸入格式:
輸入文件名為input.txt。
輸入文件的第一行為n (n<=1000),表示火星上基地的數目。接下來的n-1行每行有一個正整數,其中文件第i行的正整數為a[i],表示從編號為i的基地到編號為a[i]的基地之間有一條道路,為了更加簡潔的描述樹狀結構的基地群,有a[i]<i。
輸出格式:
輸出文件名為output.txt
輸出文件僅有一個正整數,表示至少要設立多少個消防局才有能力及時撲滅任何基地發生的火災。
輸入輸出樣例
輸入樣例#1: 復制6 1 2 3 4 5輸出樣例#1: 復制
2
記住一句話: 能實現這個目標的所有途徑都給老子找出來,拿出來互相比較大小!!! 狀態: dp[i][0]:選自己 dp[i][1]:選了至少一個兒子 dp[i][2]:選了至少一個孫子 -----------------------------------這三種是覆蓋了自己的 dp[i][3]: 兒子孫子全部覆蓋 dp[i][4]:孫子全部覆蓋 -----------------------------------這兩種並沒有覆蓋自己 建議畫一棵深度為3的完全二叉樹直觀觀察狀態 否則轉移方程比較難懂 初始轉移方程: dp[i][0] = 1+Σmin(dp[j][0...4]); 要使選了根節點之後合法(整棵子樹包括根節點被覆蓋)必須使兒子的孫子全部覆蓋 0~4狀態滿足 dp[i][1] = min( dp[k][0] + Σ(j != k)min(dp[j][0...3]) ); 要使選了一個兒子之後合法 由於兒子只可以覆蓋到兄弟 所以孫子一定要全部被覆蓋 即兒子的兒子一定覆蓋 0~3滿足 dp[i][2] = min( dp[k][1] + Σ(j != k)min(dp[j][0...2]) ); 使選了一個孫子之後合法 由於孫子最多只能覆蓋到當前節點 所以兒子一定全部覆蓋 即所有兒子本身要被覆蓋 0~2滿足 dp[i][3] = Σdp[j][0...2]; 要使兒子及孫子全部被覆蓋 即兒子本身要被覆蓋 0~2滿足 dp[i][4] = Σdp[j][0...3]; 要使孫子全部被覆蓋 即兒子的兒子要全部被覆蓋 0~3滿足 ::註意每種狀態由兒子轉移過來所以根的情況 要轉化成對於兒子來說的情況 然後改進狀態 因為每種轉移方程至少有三種可能最後取其中較小的 故時間效率較低 令dp[i][k]表示min(dp[i][0],dp[i][1]....dp[i][k])且k>=2 因為上述轉移方程最少都是0~2狀態 那麽轉移方程就大幅度化簡了: dp[i][0] = 1+Σdp[j][4]; 直接由上面變形而來 dp[i][1] = dp[i][4] + min(dp[k][0]-dp[k][3]); 選一個兒子 需保證所有孫子被覆蓋 即 dp[i][4] 然後要選出一個兒子 將他從0~3狀態變為選了自己(由於dp[i][4]中他是3狀態所以要減去一個dp[k][3]) 取這個差值最小的兒子 dp[i][2] = dp[i][3] + min(dp[k][1]-dp[k][2]); 選一個孫子 與上面類似 要保證所有兒子都被覆蓋 即dp[i][3] 再將一個兒子從0~2狀態變為0~1狀態以保證覆蓋他父節點 dp[i][3] = Σdp[j][2]; 保證所有兒子被覆蓋 兒子的0~2狀態均符合條件 dp[i][4] = Σdp[j][3]; 保證所有兒子的兒子被覆蓋 兒子的0~3狀態均符合條件 別問我為什麽dp[i][1]和dp[1][2]用到後面的狀態 因為你只需要在過程中記下那一坨min的值 把3,4處理完後再算1,2 另外由於數據特殊性 編號大的節點一定是編號小的節點的後代 所以遞推順序直接到著推就好了 代碼:(神犇的代碼稍作改動)
#include <bits/stdc++.h> using namespace std; typedef long long ll; #define inf 0x39393939 #define maxn 1010 bool G[maxn][maxn]; int dp[maxn][5]; int main() { int n; cin>>n; for(int i=2; i<=n; i++) { int x; cin>>x; G[x][i]=1; } for(int i=n; i>=1; i--) { int x1=inf,x2=inf; dp[i][0]=1; for(int j=1; j<=n; j++) if(G[i][j]) { dp[i][0]+=dp[j][4]; dp[i][3]+=dp[j][2]; dp[i][4]+=dp[j][3]; x1=min(x1,dp[j][0]-dp[j][3]); x2=min(x1,dp[j][1]-dp[j][2]); } dp[i][1]=dp[i][4]+x1; dp[i][2]=min(dp[i][3]+x2,min(dp[i][0],dp[i][1])); dp[i][3]=min(dp[i][2],dp[i][3]); dp[i][4]=min(dp[i][3],dp[i][4]); } cout<<dp[1][2]; return 0; }
//先用dfs每個節點到1號節點距離. //排序. //從距離最遠,且未標記的節點搜索4步,並標記搜到的節點,每搜索一次答案加1. #include<iostream> #include<vector> #include<algorithm> using namespace std; int n, ans; bool b[1024], c[1024]; vector<int> v[1024]; pair<int,int> q[1024]; void dfs(int x,int d){//用dfs每個節點到1號節點距離 q[x]=make_pair(d,x); for(int i=0; i<v[x].size(); i++){ if(!b[v[x][i]]){ b[v[x][i]]=1; dfs(v[x][i],d+1); b[v[x][i]]=0; } } } void dfs4(int x,int d){//搜索四步,並標記 c[x]=1; if(d==0) return; for(int i=0; i<v[x].size(); i++){ if(!b[v[x][i]]){ b[v[x][i]]=1; dfs4(v[x][i],d-1); b[v[x][i]]=0; } } } int main(){ int i, x; cin >> n; for(i=2; i<=n; i++){ cin >> x; v[i].push_back(x); v[x].push_back(i); } b[1]=1; dfs(1,0); b[1]=0; sort(q,q+n+1); for(i=n; i>0; i--){ if(!c[q[i].second]){ c[q[i].second]=1; b[q[i].second]=1; dfs4(q[i].second,4); b[q[i].second]=0; ans++; } } cout << ans << endl; return 0; }
記住一句話:能實現這個目標的所有途徑都給老子找出來,拿出來互相比較大小!!! 狀態:dp[i][0]:選自己dp[i][1]:選了至少一個兒子dp[i][2]:選了至少一個孫子-----------------------------------這三種是覆蓋了自己的dp[i][3]: 兒子孫子全部覆蓋dp[i][4]:孫子全部覆蓋-----------------------------------這兩種並沒有覆蓋自己建議畫一棵深度為3的完全二叉樹直觀觀察狀態 否則轉移方程比較難懂
初始轉移方程:dp[i][0] = 1+Σmin(dp[j][0...4]);要使選了根節點之後合法(整棵子樹包括根節點被覆蓋)必須使兒子的孫子全部覆蓋 0~4狀態滿足 dp[i][1] = min( dp[k][0] + Σ(j != k)min(dp[j][0...3]) );要使選了一個兒子之後合法 由於兒子只可以覆蓋到兄弟 所以孫子一定要全部被覆蓋 即兒子的兒子一定覆蓋 0~3滿足 dp[i][2] = min( dp[k][1] + Σ(j != k)min(dp[j][0...2]) );使選了一個孫子之後合法 由於孫子最多只能覆蓋到當前節點 所以兒子一定全部覆蓋 即所有兒子本身要被覆蓋 0~2滿足 dp[i][3] = Σdp[j][0...2];要使兒子及孫子全部被覆蓋 即兒子本身要被覆蓋 0~2滿足 dp[i][4] = Σdp[j][0...3]; 要使孫子全部被覆蓋 即兒子的兒子要全部被覆蓋 0~3滿足::註意每種狀態由兒子轉移過來所以根的情況 要轉化成對於兒子來說的情況 然後改進狀態 因為每種轉移方程至少有三種可能最後取其中較小的 故時間效率較低 令dp[i][k]表示min(dp[i][0],dp[i][1]....dp[i][k])且k>=2 因為上述轉移方程最少都是0~2狀態 那麽轉移方程就大幅度化簡了:dp[i][0] = 1+Σdp[j][4];直接由上面變形而來 dp[i][1] = dp[i][4] + min(dp[k][0]-dp[k][3]);選一個兒子 需保證所有孫子被覆蓋 即 dp[i][4] 然後要選出一個兒子 將他從0~3狀態變為選了自己(由於dp[i][4]中他是3狀態所以要減去一個dp[k][3]) 取這個差值最小的兒子dp[i][2] = dp[i][3] + min(dp[k][1]-dp[k][2]); 選一個孫子 與上面類似 要保證所有兒子都被覆蓋 即dp[i][3] 再將一個兒子從0~2狀態變為0~1狀態以保證覆蓋他父節點dp[i][3] = Σdp[j][2];保證所有兒子被覆蓋 兒子的0~2狀態均符合條件 dp[i][4] = Σdp[j][3];保證所有兒子的兒子被覆蓋 兒子的0~3狀態均符合條件
別問我為什麽dp[i][1]和dp[1][2]用到後面的狀態 因為你只需要在過程中記下那一坨min的值 把3,4處理完後再算1,2另外由於數據特殊性 編號大的節點一定是編號小的節點的後代 所以遞推順序直接到著推就好了代碼:(神犇的代碼稍作改動)
P2279 [HNOI2003]消防局的設立