POJ3254Corn Fields 状压入门题

Corn Fields

Time Limit: 2000MS

 

Memory Limit: 65536K

Total Submissions: 20061

 

Accepted: 10539

Description

Farmer John has purchased a lush new rectangular pasture composed of M by N (1 ≤ M ≤ 12; 1 ≤ N ≤ 12) square parcels. He wants to grow some yummy corn for the cows on a number of squares. Regrettably, some of the squares are infertile and can't be planted. Canny FJ knows that the cows dislike eating close to each other, so when choosing which squares to plant, he avoids choosing squares that are adjacent; no two chosen squares share an edge. He has not yet made the final choice as to which squares to plant.

Being a very open-minded man, Farmer John wants to consider all possible options for how to choose the squares for planting. He is so open-minded that he considers choosing no squares as a valid option! Please help Farmer John determine the number of ways he can choose the squares to plant.

Input

Line 1: Two space-separated integers: M and N 
Lines 2..M+1: Line i+1 describes row i of the pasture with N space-separated integers indicating whether a square is fertile (1 for fertile, 0 for infertile)

Output

Line 1: One integer: the number of ways that FJ can choose the squares modulo 100,000,000.

Sample Input

2 3
1 1 1
0 1 0

Sample Output

9

Hint

Number the squares as follows:

1 2 3

  4  


There are four ways to plant only on one squares (1, 2, 3, or 4), three ways to plant on two squares (13, 14, or 34), 1 way to plant on three squares (134), and one way to plant on no squares. 4+3+1+1=9.

Source

USACO 2006 November Gold

算法分析:

转自:https://blog.csdn.net/u013480600/article/details/19679301

题意:

有一个n*m的矩阵,这个矩阵以01的形式给出,其中标为1的格子可以选择,标为0的格子不能被选。现在要你在可以选的所有格子中选择一种方案,使得被选的任意两个格子不邻接(即无公共边)。问你共有多少种选择方案?(被你选中的格子总数没有要求,一个格子都不选也可以作为一个方案)

 

输入:第一行为n*m(1<=n,m<=12),接下来n行,每行用0,1表示矩阵的一行。

 

输出:方案总数%10^8.

分析:

本题先假设以行为计算状态转移的基本单位,则假设处理第i行时,那么第i行能选哪几个格子只和第i-1行选格子的情况有关(当然也和第i行本身哪些格子能选有关)。

 

所以我们可以得出下面的状态转移方程:令d[i][s]表示第i行的选格情况为s时的选格子的总方案数。(以s的二进制形式表示选格子的情况,如s=0011时,选择第i行的第2个和第3个格子,从左到右,从0计数列号)

 

状态转移方程:

d[i+1][s1]=sum{d[i][s2]},其中n>i>=0,s1要与s2兼容(s1与s2兼容的意思是:s1与s2对应的二进制位上不会全都是1.比如1101与0001不兼容,第3位都为1),且还要求s1的二进制位合法(比如s1为1101,而矩阵给出的该行为0111,那么s1不行,因为矩阵该行第0位只能为0,不能被选,不能为1,且要求s1的二进制形式中没有连续的两个1)。

 

初值为:d[0][s1]=1,其中s1表示所有的第0行合法值。最终我们要求的结果是d[n-1][s2],s2表示第n-1行所有的合法值。不要忘了结果对MOD求余。

注意:不放牧也是一种方法。

下面有第二种做法,比较规范。

代码实现:32ms

//注意下面代码从0行开始的
#include<cstdio>
#include<cstring>
using namespace std;
const int MOD=100000000;
long long d[15][1<<13];//d[i][s]=3,表示第i行的选格情况为s(二进制)时的总方案数。
int a[1<<13];//用来保存初始给出的每行的01矩阵
bool fine(int S,int S1)//判断S与S1是否兼容,即对应的二进制形式中,第i位是否都是1,如果是返回false
{
    for(int i=0;i<12;i++)
        if(  ( S&(1<<i) ) && ( S1&(1<<i) ) )
            return false;
    return true;
}
bool legal(int S,int S1)//判断S与S1是否合法,S1有1的位S中才能是1
{
    for(int i=0;i<12;i++)
        if(  ( S&(1<<i) ) && !( S1&(1<<i) ) )
            return false;
    return true;
}

int main()
{
    int n,m;
    while(scanf("%d%d",&n,&m)!=EOF&&n&&m)
    {
        for(int i=0; i<n; i++)
        {
            a[i]=0;
            for(int j=0; j<m; j++)   //把a[i]转化为二进制数,如 1 1 1 ,a[i]=7
            {
                int x;
                scanf("%d",&x);
                a[i]<<=1;
                if(x)a[i]|=1;
            }
        }
 
 
        memset(d,0,sizeof(d));
        long long sum = 0;//记录最后总数
        for(int S=a[0]; S>=0; S=(S-1))
        	if(legal(S,a[0])) //生成第0行所有的合法状态
        {
            bool ok = true;
            for(int j=0; j<m-1; j++) //对于该行不能有连续1相连
            {
                if(  ( S&(1<<j) ) && ( S&(1<<(j+1)) ) )
                {
                    ok = false;
                    break;
                }
            }
            if(ok)d[0][S]=1;
            if(n==1) sum = (sum+d[0][S])%MOD;//注意行数有可能为1,则只有第0行
        }
        
        for(int i=1; i<n; i++) //对于每一行
        {
            for(int S=a[i]; S>=0; S=(S-1))
            	if(legal(S,a[i])) //生成该行所有合法状态
            {
                bool ok = true;//假设状态合法
                for(int j=0; j<m-1; j++) //对于该行不能有连续1相连
                {
                    if(  ( S&(1<<j) ) && ( S&(1<<(j+1)) ) )
                    {
                        ok = false;
                        break;
                    }
                }

                if(!ok)continue;
                for(int S1=a[i-1]; S1>=0; S1=(S1-1))
                if(legal(S1,a[i-1]))       //生成上一行所有合法状态
                {
                    if(fine(S,S1))
                    {
                        d[i][S] += d[i-1][S1]%MOD;//如果兼容,则加上计数
                    }
                }
                if(i==n-1) sum = (sum +d[i][S])%MOD;
            }
        }
        printf("%I64d\n",sum);
    }
    return 0;
}

第二种比较规范的写法:

种植用1表示,不种植用0表示。每一行的情况就可以用一个二进制数state来存储。state的范围是 [0 ~ 1<< state). 
dp[i][state]表示第i行状态为state的情况下满足条件的数目。 
状态转移方程为:dp[i][state] += dp[i-1][pre_state];这个state和pre_state必须满足意义所给的条件,即左右不相邻,上下不相邻。那么 第i行状态为state的情况为第i-1行所有满足条件的状态为pre_state相加而成。 
最后的答案为最后一行所有状态的情况和相加而得。

位运算需要注意的地方: 
1.注意打括号 
2. 1 & (state>>i) 和 state & ( 1<< i ) 还是有差别的。 前者的答案只有0和1,而后者的答案有0和可能的正数。判断的时候还是要注意下写法 
3. 当前行是否相邻的判断条件是: state & (state<<1) 是否为 0 
4. 上下两行是否相邻的判断条件是: state & pre_state 是否为 0

5.代码一一解释各个二进制作用,需要细细体会

/*有一个n*m的矩阵,这个矩阵以01的形式给出,其中标为1的格子可以选择,标为0的格子不能被选。现在要你在可以选的所有格子中选择一种方案,使得被选的任意两个格子不邻接(即无公共边)。问你共有多少种选择方案?(被你选中的格子总数没有要求,一个格子都不选也可以作为一个方案)*/

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

int dp[15][1<<15];
bool is[15][15];
int n,m;
const int MOD = 1e8;

bool check(int x,int state)
{
    if( (state & (state<<1)) ) return false;  //判断当前行是否有相邻的1,很妙的思想,
                                             //00001010 00001010<<1=00010100,&就没有相邻的1
    for(int i=1;i<=m;i++) 
    {
        if( !is[x][i])             //是否合法,0表示不能放牧
        {    //二进制是从低位开始的,所以最后一位是m-i,
        	  //图为0时,state所对应的位置不能为1
            if( ((1<<(m-i)) & state) !=0 ) return false;
        }

    }
    return true;
}

int main()
{
    while( scanf("%d%d",&n,&m)!=EOF)
    {
          memset(dp,0,sizeof(dp));
          for(int i=1;i<=n;i++)
          	 for(int j = 1;j<=m;j++) 
          	   scanf("%d",&is[i][j]);

          dp[0][0] = 1;
          long long ans = 0;
          for(int i=1;i<=n;i++)    //枚举行数
          {
              for(int j = 0; j < (1<<m) ;j++) //枚举状态
              {
                  if(check(i,j)) {//判断当前行满足条件的state
                      for(int k = 0; k < (1<<m); k++ )
                      {//枚举上一行的pre_state进行更新
                           if( !(k&j) )   //判断与上一行是否冲突 ,不能有相邻的1,有相邻的1则为0
                           	 dp[i][j] += dp[i-1][k];
                      }
                  }
                  if(i==n) ans = (ans + dp[i][j]) % MOD;
              }
          }
         printf("%lld\n",ans); 
    }
    return 0;
}

更加规范的写法:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<string>
#include<algorithm>
#include<cstdlib>
#include<set>
#include<queue>
#include<stack>
#include<vector>
#include<map>
 
#define N 100010
#define Mod 100000000
#define lson l,mid,idx<<1
#define rson mid+1,r,idx<<1|1
#define lc idx<<1
#define rc idx<<1|1
const double EPS = 1e-11;
const double PI = acos ( -1.0 );
const double E = 2.718281828;
typedef long long ll;
 
const int INF = 1000010;
 
using namespace std;
 
int a[14], dp[14][1 << 12 + 1];
int s[1010];
int n, m;
int len;
 
bool ok ( int x )//判断同行是否有相邻的
{
    return !(x & (x >> 1));
}
 
void build()    //预处理
{
    len = 0;
    for ( int i = 0; i < ( 1 << m ); i++ )
    {
        if ( ok ( i ) )
        {
            s[len++] = i;
        }
    }
}
 
 
int main()
{
    while ( cin >> n >> m )
    {
        memset ( a, 0, sizeof a );
        int x;
        for ( int i = 0; i < n; i++ )
            for ( int j = 0; j < m; j++ )
            {
               scanf("%d",&x);
                if ( !x )     //111处理成000
				a[i] |= ( 1 << j );//将给出的1变成0放入数组
				//这样处理就可以把行数看作成一种状态
            }
            
        memset ( dp, 0, sizeof dp );
        build();      //预处理处理所有状态
        
        for ( int i = 0; i < len; i++ )
            if ( ! ( s[i]&a[0] ) )
                dp[0][i] = 1;
        for ( int i = 1; i < n; i++ )
        {
            for ( int j = 0; j < len; j++ )
            {
                if ( a[i] & s[j] ) //a[i]中1不可以放牧
                    continue;
                for ( int k = 0; k < len; k++ )
                {
                    if ( s[k]&s[j] || ( s[k]&a[i - 1] ) )//判断行之间是否冲突和与原状态是否冲突
                        continue;
                    dp[i][j] += dp[i - 1][k];
                    dp[i][j] %= Mod;
                }
            }
        }
        int ans = 0;
        for ( int i = 0; i < len; i++ )
        {
            ans += dp[n - 1][i];
            ans %= Mod;
        }
        cout << ans << endl;
    }
    return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值