zoukankan      html  css  js  c++  java
  • [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
    ", x)
    #define Prl(x) printf("%lld
    ",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
    ", x)
    #define Prl(x) printf("%lld
    ",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;
    }
  • 相关阅读:
    hdu 5101 Select
    hdu 5100 Chessboard
    cf B. I.O.U.
    cf C. Inna and Dima
    cf B. Inna and Nine
    cf C. Counting Kangaroos is Fun
    Radar Installation 贪心
    spfa模板
    Sequence
    棋盘问题
  • 原文地址:https://www.cnblogs.com/Hugh-Locke/p/10261232.html
Copyright © 2011-2022 走看看