poj 3254 简单入门状压DP

Corn Fields

Time Limit: 2000MS   Memory Limit: 65536K
Total Submissions: 19670   Accepted: 10318

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.

题目大意:给你一片N*M的田地,规定是‘1’的地方是可以种玉米的,当然也就能放牧,‘0’的地方是两者都不行的,其实这道题,主要是问你能种玉米的地方有多少种情况,跟牛没有多大的关系,但是还有一定的约束条件就是不能在相邻的‘1’处种植,相邻是指行和列相邻

题解:怎么做呢,我感觉是dp就可以爆搜呗,但是都说是状压DP,那就按状压DP来,今天第一次学习状压DP有点绕,

规定dp[i][j]表示第i行第j种情况,什么叫第j种情况??状压dp是就是将整型的数转化为二进制数的应用,在加上集合的思想

下面从代码中说第j种情况是怎么来的

#include<iostream>
#include<cstring>
using namespace std;
typedef long long ll;
#define rep(i,a,b) for(int i=a;i<=b;i++)
const int MOD= 100000000;
int dp[15][1<<15];
int a[15];//用来存储题目给的1或0的组合,并将其转化为int型
int main(){

  char s[200];
  ios::sync_with_stdio(0);
  cin.tie(0),cout.tie(0);
  int N,M;
  int x;
  cin>>N>>M;
  rep(i,1,N)
  for(int j=0;j<M;j++)
  {
      cin>>x;
      a[i]|=x<<j;//或运算相当于将数据并起来,比如第一组数据111,
  }//在a[1]中就是7,它是由001|010|100来得到的
//  rep(i,1,N)
//  cout<<a[i]<<" ";
//  cout<<endl;
  for(int i=0;i<(1<<M);i++)//(1<<M)必须要加括号,因为牵扯到运算符的优先级问题,包括下面有
  {//加减乘除等号运算符都是加括号,为啥是1<<M呢因为给我们的数据就是1,0的组合数,我们将其转化为
//二进制来用,就是将所有的M列情况列举出来,看看是否满足下面的if语句,也可以写成i<=((1<<M)-1)
//这样就保证是从000~111二进制的进位情况,同时就是所有的田地能否种植的情况
      if(((i&a[1])==i)&&((i&(i<<1))==0))//“i&a[1])==i”是看此时的i是否是a[1]的子集,也就是
         dp[1][i]=1;//111的子集,可以手动模拟一下,算是新知识吧,这样就满足了所有的符合在111的
  }//情况中与不中的条件(只有这一个条件话000~111都是子集),还有一个约束条件就是“i&(i<<1)”这是
//保证了相邻都是1的情况是不能种的,(101)&(101<<1)==(0101)&(1010)==0,满足题意,i=011,就
//不行,这样位运算的结果就不是0了,可能这就是这道题用状压DP的原因
  rep(i,2,N)
  {
      for(int s=0;s<(1<<M);s++)
      {
          if((s&a[i])==s && ((s&(s<<1))==0))
          {
//s0表示上一行,s表示当前行,上面的fof循环是对dp数组的处理,这里的for与面的意思一样,主要就是
//下面的for循环,下面的是同样是将s0从0~(1<<M)循环一遍,看有没有符合的情况
              for(int s0=0;s0<(1<<M);s0++)
                if((s0&s)==0)//判断是否和上面的一行同为1,不是同为1就能将s0的情况加上
//其实模拟一下就会发现当s=001(010的子集)时,s0为110也是满足if语句的,不过dp[i-1][s0]=0
//下面会有优化的地方
                dp[i][s]=(dp[i][s]+dp[i-1][s0])%MOD;
          }
      }
  }
//  for(int i=1;i<=N;i++)
//  {
//      for(int j=0;j<(1<<M);j++)
//        cout<<dp[i][j]<<" ";
//      cout<<endl;
//  }
  ll ans=0;
  for(int i=0;i<(1<<M);i++)
  ans=(ans+dp[N][i])%MOD;
  cout<<ans<<endl;
    return 0;
}
#include<iostream>
#include<cstring>
using namespace std;
typedef long long ll;
#define rep(i,a,b) for(int i=a;i<=b;i++)
const int MOD= 100000000;
int dp[15][1<<15];
int a[15];
int main(){

  char s[200];
  ios::sync_with_stdio(0);
  cin.tie(0),cout.tie(0);
  int N,M;
  int x;
  cin>>N>>M;
  rep(i,1,N)
  for(int j=0;j<M;j++)
  {
      cin>>x;
      a[i]|=x<<j;
  }
//  rep(i,1,N)
//  cout<<a[i]<<" ";
//  cout<<endl;
  for(int i=0;i<(1<<M);i++)
  {
      if(((i&a[1])==i)&&((i&(i<<1))==0))
         dp[1][i]=1;
  }
  rep(i,2,N)
  {
      for(int s=0;s<(1<<M);s++)
      {
          if((s&a[i])==s && ((s&(s<<1))==0))
          {
              int s1=((1<<M)-1)^s;//这里的s1是取s的补集(要注意运算等级),就不用枚举s了
              for(int s0=s1;;s0=s1&s0-1){//此处的s0是集合的意思了不能在s0--,s0=s1&s0-1
                if((s0&s)==0){//的意思是枚举s1的子集,是s0-1再&s1
                dp[i][s]=(dp[i][s]+dp[i-1][s0])%MOD;
                }
                if(s0==0) break;//要枚举000这种情况,所以s0==0放在这里
              }
          }
      }
  }
//  for(int i=1;i<=N;i++)
//  {
//      for(int j=0;j<(1<<M);j++)
//        cout<<dp[i][j]<<" ";
//      cout<<endl;
//  }
  ll ans=0;
  for(int i=0;i<(1<<M);i++)
  ans=(ans+dp[N][i])%MOD;
  cout<<ans<<endl;
    return 0;
}

猜你喜欢

转载自blog.csdn.net/c___c18/article/details/81507203
今日推荐