【状态压缩DP】【POJ3254】【POJ1185】 入门题

【POJ3254】题目直达车: POJ 3254 Corn Fields

题意:有一个m*n(n、m<=12)的网格,每个方格只能是0、1,从网格中选一部分

(可为0)方格(只能选1的方格),不能取相邻(包括四个方向)的两个,

问有多少种选法。

分析:因为n、m的数据比较小,所以可用状态DP,即:用一个整数p表示一行选方格

      的情况,p的二进制为1时说明这位置的方格被选,反之不选。用时,将每一行

      的方格也当成二进制化为一个整数q,但为了更好地判断状态p的可行性,可以将

      网格中的0、1互换,如:0 1 0  =>  1 0 1, 则q=5。那么只考虑这一行时,判断

      p的可行性的方法是:当(p&q)=0时表示可以这么选(即p状态),反之不能。

      那么,判断相邻两行的状态可行性的方法如下:

      设第i-1行状态为pi-1,第i行状态为pi,则:当(pi-1&pi)=0时为可行,反之则不行。

      转移方程:

          

代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;
const int mod=1000000000;
int n,m;
vector<int>M;      // 状态
int a[15], p[15]={0};
long long dp[13][4500];
void dfs(int x) {  // 找出长度为n的所有状态
    if(x==-1) {
        int u=0;
        for(int i=0; i<n; ++i) if(a[i]) u|=(1<<i);
        M.push_back(u); return;
    }
    for(int i=0; i<=1; ++i){
        if(i&&a[x+1]) break;
        a[x]=i;  dfs(x-1);
    }
}
int main() {
    while(~scanf("%d%d",&m,&n)) {
        memset(a,0,sizeof(a));
        memset(dp,0,sizeof(dp));
        M.clear();
        dfs(n-1);
        dp[0][0]=1LL;
        int len=M.size();
        for(int i=1; i<=m; ++i) {
            p[i]=0;
            for(int j=0; j<n; ++j){
                int x; scanf("%d",&x);
                p[i]|=(1-x)<<j;
            }
            for(int j=0; j<len; ++j)
                if((M[j]&p[i])==0)
                    for(int k=0; k<len; ++k)
                        if((M[k]&p[i-1])==0&&(M[j]&M[k])==0)
                            dp[i][j]=(dp[i][j]+dp[i-1][k])%mod;
        }
        long long ans=0;
        for(int j=0; j<len; ++j) ans=(ans+dp[m][j])%mod;
        printf("%lld\n", ans);
    }
    return 0;
}



【POJ1185】题目直达车:   POJ 1185 炮兵阵地

分析:

列( <=10 )的数据比较小, 一般会想到状压DP.

Ⅰ、如果一行10全个‘P’,满足题意的状态不超过60种(可手动枚举)。

Ⅱ、用DFS搜出所有可能表示状态的整数(二进制1表示可以放,0则不能)。

Ⅲ、对每一行的地行进行状态处理(p[i]表示第i行地形的状态),二进制‘H’转1,‘P’转0;

Ⅳ、用dp[i][j][k]表示第i行,且i行状态为j,i-1行状态为k时,最多能放置的量。

Ⅴ、对于第i行的可行状态必须满足:

               ⒈  j & k =0 且 j & t =0 (t为第i-2行放置状态)与前两行匹配,即不互相攻击。

               ⒉  j & p[i] = 0 与地形匹配,即只放平原地带。

Ⅵ、转移方程:dp[i][j][k]=max(dp[i][j][k],dp[i-1][k][t]+num[j])

      (num[j]为第j种状态中放置炮兵的数量)   

第一次做状态DP,  参考(题解报告)的思路才敲出来

代码: 

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<vector>
using namespace std;
int p[105],dp[105][65][65];
vector<int>M[11];
int a[11],n,m;
void DFS(int k,int t){  ///搜索m=t时如果全为'P'可以表示状态的整数
    if(k==t){
        int p=0;
        for(int i=0;i<t;++i)
            if(a[i]) p=p|(1<<i);
        M[t].push_back(p);  /// 记下能表示状态的整数
        return;
    }
    for(int i=0;i<=1;++i){ 
        if(i&&((k&&a[k-1])||(k>1&&(a[k-1]||a[k-2])))) continue;
        a[k]=i;
        DFS(k+1,t);
    }
}
int main(){  
    for(int i=1;i<=10;++i) DFS(0,i); 
    while(~scanf("%d%d",&n,&m)&&n+m){
        p[0]=0; 
        ///num[i]记下用第i种状态(整数M[m][i]表示) 中设了多少炮兵部队
        int num[65], len=M[m].size();
        for(int i=0;i<len;++i)
            num[i]=__builtin_popcount( M[m][i] ); ///计算M[m][i]中二进制中1的个数 
        memset(dp,0,sizeof(dp));
        int Max=0;
        for(int i=1;i<=n;++i){
            char ch[105]; scanf("%s",ch);
            p[i]=0;
            //预处理地形(1代表'H',0代表'P')
            for(int j=0;j<m;++j) if(ch[j]=='H') p[i]|=(1<<j);
            if(i==1){ //初始化第一行所有布置情况
                for(int k=0; k<len; ++k)
                    for(int j=0; j<len; ++j){
                        if(!(M[m][k]&p[1])) dp[1][k][j]=num[k];
                        else dp[1][k][j]=0;
                        Max=max(Max,dp[1][k][j]);
                    }
                continue;
            }
            for(int j=0; j<len; ++j)//当前行
                if(!(M[m][j]&p[i])) //与 地形匹配
                    for(int k=0; k<len; ++k)//i-1
                        if(!(M[m][k]&p[i-1])&&!(M[m][k]&M[m][j]))//与 地形、i-1行匹配
                            for(int t=0; t<len; ++t)//i-2
                                //与 地形、i-1行、i-2行匹配
                                if(!(M[m][t]&p[i-2])&&!(M[m][t]&M[m][k])&&!(M[m][t]&M[m][j])){ 
                                    dp[i][j][k]=max(dp[i][j][k],dp[i-1][k][t]+num[j]);
                                    if(i==n)Max=max(dp[i][j][k],Max);
                                }           
        }
        printf("%d\n",Max);
    }
    return 0;
}




  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值