• 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;
    }
  • 相关阅读:
    屏蔽浏览器的脚本错误提示
    服务器定时重启计划任务
    卖程序的小女孩(转)
    会话状态已创建一个会话 ID,但由于响应已被应用程序刷新而无法保存它。
    Sesion空框架跳到登陆js
    poj 1679 The Unique MST (次小生成树 模版)
    hdu 3488 Tour (km 二分图 最小权)
    sdut 2401 最大矩形面积
    poj 3422 Kaka's Matrix Travels (最大费用流(最小费用最大流)+++拆点)
    博弈论 简介
  • 原文地址:https://www.cnblogs.com/Hugh-Locke/p/9499717.html
Copyright © 2020-2023  润新知