实现思路:用一个二进制数来表示当前状态。
例题:
一个n*m的棋盘,要任意摆放若干个棋子,求互不相邻,求方案数,输出结果对1e9+7取余。
思路:
NO.1 这道题,如果用常规方法做,应该考虑每个点上与上一个点的关系,这样做时间复杂度高,每一部需要考虑的情况多。
NO.2 先将这道题弱化一下,考虑一个1*n的棋盘。
我们用1和0来表示两种状态,分别是放和不放,那当前棋盘的状态表示为一个n位的二进制数。即101就表示1*3的棋盘上第一个和第三个放。该棋盘的状态总数就有2^n个,分别是从0000...0(n个0)到1111...1(n个1),遍历过程就由0遍历到1<<n,依次判断该状态是否合法,若合法便将总数加一。
对于状态是否合法的判断方法是将该状态进行向左移位,再与该状态进行&运算。若能算出非零结果则说明该状态有相邻情况,跳过;反之,则状态合法将数据更新。
eg.1 101&1010(合法) eg.2 1011&10110(非法)
1010 10110
& 101 & 1011
—————————— ——————————
0000 0010
代码如下:
#include<cstdio>
using namespace std;
int n,ans;
int main(){
scanf("%d",&n);
for(int i=0;i<(1<<n);i++){
if(i&(i<<1))continue;
ans++;
}printf("%d\n",ans);
return 0;
}
这是一个维的状态递推,现在考虑将1*n转变为n*m,完成思路不过就是将1*m的递推执行n次,但期间要注意每行之间的相邻的放置情况。
代码如下:
#include<cstdio>
using namespace std;
const int N=1e3+10;
const int MOD=1e9+7;
int n,m,ans;
int dp[N][N];
int main(){
scanf("%d%d",&n,&m);
dp[0][0]=1;
for(int i=1;i<=n;i++){
for(int j=0;j<(1<<m);j++){
if(j&(j<<1))continue;//状态不合法直接跳过
for(int k=0;k<(1<<m);k++){
//k表示上一行的状态
if(j&k)continue;//判断上下相邻
if(k&(k<<1))continue;//状态不合法直接跳过
dp[i][j]=(dp[i][j]+dp[i-1][k])%MOD;//更新当前状态的总数
}
}
}for(int i=0;i<(1<<m);i++){
ans=(ans+dp[n][i])%MOD;//统计结果
}printf("%d\n",ans);
return 0;
}