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;
    }
  • 相关阅读:
    修改MySQL密码
    struts入门
    监听
    游戏外挂教程(转)
    “无法加载一个或多个请求的类型。有关更多信息,请检索 LoaderExceptions 属性 “之解决
    C# PropertyGrid控件应用心得
    登录时的"记住我"
    自动登录、记住我(保存登陆状态)实现
    UpdatePanel的使用方法
    asp.net中使用基于角色role的Forms验证
  • 原文地址:https://www.cnblogs.com/Hugh-Locke/p/10261232.html
Copyright © 2011-2022 走看看