1. 程式人生 > >洛谷 P2279 [HNOI2003]消防局的設立 貪心

洛谷 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,一頭撞死後參考大佬題解才發現貪心來著(樹形DP也可以但是貪心更簡單)
明確怎麼貪,對於每一棵樹,它最深的節點肯定是要被覆蓋的,我們從這裡入手;對於一個最深的節點來說,可以在它這裡建一個消防站,也可以在它的父親那裡建一個消防站,還可以在它祖父那裡建一個消防站,不難想到,在祖父處

建一個消防站就可以覆蓋最多的節點,由於是從最底層開始的,滿足貪心的性質,策略成立

那怎麼判斷一個點是否被覆蓋呢,對於本題中的每一個點,可以覆蓋它的節點有:它的祖父,它的父親,它的兄弟,它的兒子,它的孫子,除了兄弟之外的情況都可以用f[]陣列來維護,那該怎麼處理兄弟的情況呢?

我們引入一個o陣列,o[i]表示裡i距離最近的消防站到i的距離,當一個節點的o[父親]==1時,它一定被覆蓋,這樣就解決了兄弟的問題
詳見程式碼註釋
完整程式碼
#include<bits/stdc++.h>
using namespace std;
const int MAXN=1005;
int b[MAXN],o[MAXN],d[MAXN],f[MAXN],n,ans,u,fa,ffa;//u,fa,ffa分別表示當前節點,它的父親,它的祖父
bool cmp(int i,int j){return d[i]>d[j];}
int main()
{
    scanf("%d",&n);
    b[1]=1;o[1]=o[0]=0x3f;d[1]=0;//可以發現有沒有根對這個問題影響不大,轉化為無根樹
    for(int
i=2;i<=n;i++)scanf("%d",&f[i]),d[i]=d[f[i]]+1,b[i]=i,o[i]=0x3f; sort(b+1,b+n+1,cmp);//按深度排序 for(int i=1;i<=n;i++) { u=b[i];fa=f[u];ffa=f[f[u]];//依次取出 o[u]=min(o[u],min(o[fa]+1,o[ffa]+2));//用當前節點的父親和祖父更新o[]陣列 if(o[u]>2)//o[u]>2的話說明沒有消防站可以覆蓋到當前節點 { ans++;o[ffa]=0;//在它的祖父處新建一個消防站 o[f[ffa]]=min(o[f[ffa]],1);o[f[f[ffa]]]=min(o[f[f[ffa]]],2);//向上更新它的父親和祖父 } } cout<<ans;//愉快的輸出 return 0; }

 參考大佬@BJpers2 的題解