1. 程式人生 > >[HNOI2003]消防局的設立 樹形dp // 貪心

[HNOI2003]消防局的設立 樹形dp // 貪心

https://www.luogu.org/problemnew/show/P2279

一開始就想到了貪心的方法,不過一直覺得不能證明。

貪心的考慮是在深度從深到淺遍歷每個結點的過程中,對於每個沒有覆蓋的結點選擇覆蓋他的祖父結點。

仔細想想覺得這是正確的。

在實現的過程中有一個小技巧是o[i]記錄i結點距離消防局最近的距離,如果o[i] > 2則需要在他的祖父結點建立一個消防站。用這種方法可以很方便的判斷兄弟節點是否被覆蓋。

一個細節是要給根節點1建立兩個虛結點N + 1和N + 2作為他的父親結點和祖父結點,不然在1結點尋找祖父節點的時候容易出現問題。

用這樣的方法也可以在常數很小的時間內實現樹上半徑k的覆蓋問題

#include <map>
#include <set>
#include <ctime>
#include <cmath>
#include <queue>
#include <stack>
#include <vector>
#include <string>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <sstream>
#include 
<iostream> #include <algorithm> #include <functional> using namespace std; inline int read(){int now=0;register char c=getchar();for(;!isdigit(c);c=getchar()); for(;isdigit(c);now=now*10+c-'0',c=getchar());return now;} #define For(i, x, y) for(int i=x;i<=y;i++) #define _For(i, x, y) for(int i=x;i>=y;i--) #define
Mem(f, x) memset(f,x,sizeof(f)) #define Sca(x) scanf("%d", &x) #define Sca2(x,y) scanf("%d%d",&x,&y) #define Sca3(x,y,z) scanf("%d%d%d",&x,&y,&z) #define Scl(x) scanf("%lld",&x); #define Pri(x) printf("%d\n", x) #define Prl(x) printf("%lld\n",x); #define CLR(u) for(int i=0;i<=N;i++)u[i].clear(); #define LL long long #define ULL unsigned long long #define mp make_pair #define PII pair<int,int> #define PIL pair<int,long long> #define PLL pair<long long,long long> #define pb push_back #define fi first #define se second typedef vector<int> VI; const double eps = 1e-9; const int maxn =2010; const int INF = 0x3f3f3f3f; const int mod = 1e9 + 7; int N,M,K; int fa[maxn]; int deep[maxn]; int a[maxn]; int o[maxn]; bool cmp(int a,int b){ return deep[a] > deep[b]; } int main(){ Sca(N); o[1] = INF; fa[1] = N + 1; fa[N + 1] = N + 2; o[N + 1] = o[N + 2] = INF; for(int i = 1; i <= N; i ++) a[i] = i; for(int i = 2; i <= N ; i ++){ Sca(fa[i]); deep[i] = deep[fa[i]] + 1; o[i] = INF; } sort(a + 1,a + 1 + N,cmp); int ans = 0; for(int i = 1; i <= N ; i ++){ int u = a[i]; int v = fa[u],w = fa[fa[u]]; o[u] = min(o[u],min(o[v] + 1,o[w] + 2)); if(o[u] > 2){ o[w] = 0; o[fa[w]] = min(o[fa[u]],1); o[fa[fa[w]]] = min(o[fa[fa[w]]],2); ans++; } } Pri(ans); return 0; }
貪心

當然這道題出現在dp專題裡,事實上也是一個很硬核的樹dp,在沒有想到或者證明貪心的時候,這樣明顯的樹dp也不失為一種做法。

思路借鑑了luogu的題解,開始對於這樣的樹dp一直想著兩次樹dp,第一次記錄根節點的answer,第二次利用根節點拓展到整棵樹的answer,導致開始的思路僅限於dp[maxn][3]來記錄這個點,這個點的兒子,這個點的孫子的消防站情況,使得第一次dfs就遇到了問題,可能可以做,但有些麻煩。

看了題解之後茅塞頓開,事實上樹dp並不需要套路的總是兩邊dp,換一種思路同時維護兩端的情況,就是除了原本的3個狀態以外,重新記錄dp[i][3]表示所有兒子都被覆蓋的情況以及dp[i][4]所有孫子都被覆蓋的情況。

僅用一遍dfs就可以解決這個問題。

#include <map>
#include <set>
#include <ctime>
#include <cmath>
#include <queue>
#include <stack>
#include <vector>
#include <string>
#include <cstdio>
#include<algorithm> 
#include <cstdlib>
#include <cstring>
#include <sstream>
#include <iostream>
#include <functional>
using namespace std;
inline int read(){int now=0;register char c=getchar();for(;!isdigit(c);c=getchar());
for(;isdigit(c);now=now*10+c-'0',c=getchar());return now;}
#define For(i, x, y) for(int i=x;i<=y;i++)  
#define _For(i, x, y) for(int i=x;i>=y;i--)
#define Mem(f, x) memset(f,x,sizeof(f))  
#define Sca(x) scanf("%d", &x)
#define Sca2(x,y) scanf("%d%d",&x,&y)
#define Sca3(x,y,z) scanf("%d%d%d",&x,&y,&z)
#define Scl(x) scanf("%lld",&x);  
#define Pri(x) printf("%d\n", x)
#define Prl(x) printf("%lld\n",x);  
#define CLR(u) for(int i=0;i<=N;i++)u[i].clear();
#define LL long long
#define ULL unsigned long long  
#define mp make_pair
#define PII pair<int,int>
#define PIL pair<int,long long>
#define PLL pair<long long,long long>
#define pb push_back
#define fi first
#define se second 
typedef vector<int> VI;
const double eps = 1e-9;
const int maxn = 1010;
const int INF = 0x3f3f3f3f;
const int mod = 1e9 + 7; 
int N,M,K;
struct Edge{
    int to,next;
}edge[maxn * 2];
int head[maxn],tot;
void init(){
    for(int i = 1; i <= N ; i ++) head[i] = -1;
    tot = 0;
}
void add(int u,int v){
    edge[tot].to = v;
    edge[tot].next = head[u];
    head[u] = tot++;
}
int dp[maxn][5]; // 0這是,1兒子是,2孫子是,3兒子全被覆蓋,4孫子全被覆蓋 
int Min(int a,int b){
    return a > b?b:a;
}
int Min(int a,int b,int c){
    return Min(Min(a,b),c);
} 
int Min(int a,int b,int c,int d){
    return Min(Min(a,b,c),d);
}
int Min(int a,int b,int c,int d,int e){
    return Min(Min(a,b,c),Min(d,e));
}
void dfs(int t){
    if(head[t] == -1){
        dp[t][0] = 1;
        dp[t][1] = dp[t][2] = INF;
        dp[t][3] = dp[t][4] = 0;
        return;
    }
    dp[t][0] = 1;
    int MAXSON = INF;
    int MAXGS = INF;
    for(int i = head[t]; ~i; i = edge[i].next){
        int v = edge[i].to;
        dfs(v);
        dp[t][0] += Min(dp[v][0],dp[v][1],dp[v][2],dp[v][3],dp[v][4]);
        dp[t][1] += Min(dp[v][0],dp[v][1],dp[v][2],dp[v][3]);
        MAXSON = Min(dp[v][0] - Min(dp[v][0],dp[v][1],dp[v][2],dp[v][3]),MAXSON);
        MAXGS = Min(dp[v][1] - Min(dp[v][0],dp[v][1],dp[v][2]),MAXGS);
        dp[t][2] += Min(dp[v][0],dp[v][1],dp[v][2]);
        dp[t][3] += Min(dp[v][0],dp[v][1],dp[v][2]);
        dp[t][4] += Min(dp[v][0],dp[v][1],dp[v][2],dp[v][3]);
    }
    dp[t][1] += MAXSON;
    dp[t][2] += MAXGS;
}
int main(){
    Sca(N); init();
    for(int i = 2; i <= N; i ++){
        int x; Sca(x);
        add(x,i);
    }
    dfs(1);
    cout << Min(dp[1][0],dp[1][2],dp[1][1]);
    return 0;
}