zoukankan      html  css  js  c++  java
  • POJ1185 状压dp(二进制//三进制)解法

    很显然这是一道状压dp的题目

    由于每个最优子结构和前两行有关,一个显而易见的想法是用三维dp[i][j][k]用来记录在第i行下为j状态,i - 1行为k状态时的最大值,然而dp[100][1 << 11][1 << 11]显然是要MLE的,我们可以想到用滚动数组优化,事实上确实可以用滚动数组优化。然而 在时间复杂度上 100 * 1024 * 1024 * 1024也是一个不可能补TLE的数字,一个不那么显然的办法是预处理出所有可行的状态,经过看题解或者写个暴力炸一下之后可以知道这些状态并不超过70,也就是说时间复杂度可以优化到100 * 70^3,这就看起来很合情合理了,数组也不用上滚动数组直接跑就好了。

    剩下的就是实现的问题了。

    用一个state数组预处理出所有的合法状态(在不考虑高地不高地的情况下)

    用一个base数组预处理出所有高地的状态(高地为1,平地为0)当state中的状态 & 上base中的状态不为0时,代表有一个小兵站在了高地上,这是不被允许的,就要跳过这个状态。

    用一个solider数组预处理出所有合法状态下的小兵数目,左右是省的每次都计算一下有几个小兵,不但让这个程序跑起来很快,也让我们看起来很帅。

    附上这个解决方法的代码。

    #include <map>
    #include <set>
    #include <cmath>
    #include <queue>
    #include <stack>
    #include <vector>
    #include <string>
    #include <cstdio>
    #include <cstdlib>
    #include <cstring>
    #include <sstream>
    #include <iostream>
    #include <algorithm>
    #include <functional>
    #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 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 
    using namespace std;
    typedef vector<int> VI;
    const double eps = 1e-9;
    const int maxn = 110;
    const int INF = 0x3f3f3f3f;
    const int mod = 1e9 + 7; 
    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;
    }
    int N,M;
    char MAP[maxn][15];
    int state[maxn];          //所有合法状态 
    LL dp[2][maxn][maxn];   //在i行第j状态以及i- 1行第k状态下的最大值 
    LL solider[maxn];  //在这个状态下的士兵 
    int base[maxn];    // 原地图的的第i个原状态 
    int cnt;          //合法状态的数目 
    int main()
    {
        while(~scanf("%d%d",&N,&M)){
            Mem(base,0); Mem(solider,0); Mem(state,0); Mem(dp,0);
            cnt = 0;
            For(i,1,N){
                scanf("%s",&MAP[i]);
            //    cout << MAP[i] << endl;
                for(int j = 0; j < M ; j ++){
                    if(MAP[i][j] == 'H') base[i] += 1 << j;
                }
            }
            for(int i = 0 ; i < 1 << M; i ++){
                if((i & (i << 1)) || (i & (i << 2))) continue;
                state[++cnt] = i;
                int k = i;
                while(k){
                    solider[cnt] += k & 1;
                    k >>= 1;
                }
            }
            For(i,0,cnt){
            //    cout << solider[i] << endl;
                if(base[1] & state[i]) continue;
                dp[1][i][0] = solider[i];
            }
            For(i,0,cnt){
                if(base[2] & state[i]) continue;
                For(j,1,cnt){
                    if(base[1] & state[j] || state[i] & state[j]) continue;
                    dp[0][i][j] = max(dp[1][j][0] + solider[i],dp[0][i][j]);
                }
            }
            For(i,3,N){
                For(j,0,cnt){
                    if(base[i] & state[j]) continue;
                    For(k,0,cnt){
                        if(base[i - 1] & state[k] || state[j] & state[k]) continue;
                        For(p,0,cnt){
                            if(base[i - 2] & state[p] || state[p] & state[k] || state[j] & state[p]) continue;
                            dp[i & 1][j][k] = max(dp[i & 1][j][k],dp[i + 1 & 1][k][p] + solider[j]);
                        }
                    }
                }
            }
            LL MAX = 0;
            For(i,0,cnt){
                For(j,0,cnt){
                    MAX = max(MAX,dp[N & 1][i][j]);
                }
            }
            Prl(MAX);
        } 
        return 0;
    }

     事实上除了以上这种巧妙地方法之外,我们依然有更暴力但是却更难写的方法,就是将二进制状态压缩改为三进制的状态压缩。

    我们假设在放置一个小兵之后会产生一个“缓冲带”,导致下面的这个状态变为2,下下面的状态变为1,再下面变回0,意味着缓冲区结束,这里可以继续放置小兵。但是仔细一想发现这样构成的状态并不是那么好写状态转移方程,我们从记忆话搜索里得到灵感,考虑直接dfs暴搜。

    由于经过了状态压缩,dfs的状态转移并不那么困难,我们用一个整数cur来表示此时的状态,用一个next来表示下一行的状态,

    每次的转移主要是横向的转移,当到了行末尾的时候转移到下一行,此时cur的值变为next,next的值变为0,到最后一行时开始返回,更新回答案。

    像这样的状态表示比较复杂,冗余的不合法状态较多的题目,不一定要写出确切的状态转移方程,而用dfs也可以很好的解决问题,不过在这题上的效率并不是很理想,上面的400ms,这个1600ms,主要提供遇到问题的解决思路。

    #include <map>
    #include <set>
    #include <cmath>
    #include <queue>
    #include <stack>
    #include <vector>
    #include <string>
    #include <cstdio>
    #include <cstdlib>
    #include <cstring>
    #include <sstream>
    #include <iostream>
    #include <algorithm>
    #include <functional>
    #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 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 
    using namespace std;
    typedef vector<int> VI;
    const double eps = 1e-9;
    const int maxn = 110;
    const int INF = 0x3f3f3f3f;
    const int mod = 1e9 + 7; 
    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;
    }
    int N,M;
    int dp[maxn][60000];
    char MAP[maxn][15];
    int power[10]={1,3,9,27,81,243,729,2187,6561,19683};
    int getbit(int i,int pos){
        if(pos == 0) return i % 3;
        if(pos >= M) return 0;
        if(i >= power[pos]){
            return (i / power[pos]) % 3;
        }
        return 0;
    }
    //x,y为横纵坐标,cur为上两行的状态,next为下一状态,cnt为记录x行已放置的 
    void dfs(int x,int y,int cur,int next,int cnt)
    {
        if(!y){    //刚进入当前行 
            if(dp[x][cur] != -1) return;
            dp[x][cur] = 0;
        }
        if(y >= M){  //已经到行末尾 
            if(x < N - 1){
                dfs(x + 1,0,next,0,0);   //转变为下一行,下一行状态转变为当前状态,下一行状态初始化为0 
                dp[x][cur] = max(dp[x][cur],cnt + dp[x + 1][next]);  //从上一个状态更新这个状态 
            }else{
                dp[x][cur] = max(dp[x][cur],cnt);  //由于没有下一个状态,这个状态的最大值就是他自己 
            }
            return;
        } 
        int i = getbit(cur,y);    //这个点的值 
        if(!i && MAP[x][y] == 'P'){        //这个点可放小兵 
            int j = 2 * power[y],k;       //在这个点放了小兵之后next要增加的值,也就是下边增加一个2 
            k = getbit(cur,y + 1); 
            if(k == 2){                   //由于这个点的右边上面刚放过一个小兵,右边要增加1 
                j += power[y + 1];
            }
            k = getbit(cur,y + 2);       //同理这个点右边的右边的上面放过一个小兵 
            if(k == 2){
                j += power[y + 2];
            }
            dfs(x,y + 3,cur,next + j,cnt + 1);     //这个点放了小兵 
            dfs(x,y + 1,cur,next,cnt);             //这个点不放小兵 
            return;
        }
        if(i == 2) dfs(x,y + 1,cur,next + power[y],cnt);   //下面为1 
        else dfs(x,y + 1,cur,next,cnt);           //下面为0 
    }
    int main()
    {
        while(~scanf("%d%d",&N,&M)){
            for(int i = 0; i < N ; i ++){
                scanf("%s",MAP[i]);
            }
            Mem(dp,-1);
            dfs(0,0,0,0,0);
            Pri(dp[0][0]);
        }
        return 0;
    }
  • 相关阅读:
    Oracle 11g SQL Fundamentals Training Introduction02
    Chapter 05Reporting Aggregated data Using the Group Functions 01
    Chapter 01Restriicting Data Using The SQL SELECT Statemnt01
    Oracle 11g SQL Fundamentals Training Introduction01
    Chapter 04Using Conversion Functions and Conditional ExpressionsConditional Expressions
    Unix时代的开创者Ken Thompson (zz.is2120.bg57iv3)
    我心目中计算机软件科学最小必读书目 (zz.is2120)
    北京将评估分时分区单双号限行 推进错时上下班 (zz)
    佳能G系列领军相机G1X
    选购单反相机的新建议——心民谈宾得K5(转)
  • 原文地址:https://www.cnblogs.com/Hugh-Locke/p/9499717.html
Copyright © 2011-2022 走看看