棋盘式的状态压缩DP也称为基于连通性的状态压缩DP。一般是遍历每一行,再枚举当前行的状态,最后枚举与当前行合法的上一行的状态
291. 蒙德里安的梦想
求把N*M的棋盘分割成若干个1*2的的长方形,有多少种方案。
例如当N=2,M=4时,共有5种方案。当N=2,M=3时,共有3种方案。
如下图所示:
输入格式
输入包含多组测试用例。
每组测试用例占一行,包含两个整数N和M。
当输入用例N=0,M=0时,表示输入终止,且该用例无需处理。
输出格式
每个测试用例输出一个结果,每个结果占一行。
数据范围
1≤N,M≤11
输入样例:
1 2
1 3
1 4
2 2
2 3
2 4
2 11
4 11
0 0
输出样例:
1
0
1
2
3
5
144
51205
思路:
(f[i,j]),(i)表示行,(j)表示状态,状态中的(0)表示这一行的木块在下一行不凸出,(1)表示在下一行凸出。我们只在乎一行的平凸情况,最后自取n行的全平情况,这集合也就是题目的答案,达到了不重不漏。那么当从当前枚举状态时需满足:当前行的状态和上一行的状态同一个列位置不能有两个凸出,以及当前行的横放木块要合理,也就是对于上一行不凸出的列位置和当前行也不凸出的列位置放的一定是横着的木块,那么连续的横着的个数应该是偶数。
#include<bits/stdc++.h>
using namespace std;
const int N=12;
long long f[N][1<<N];// 1表示在下一行有凸出,0表示在下一行没有凸出
int n,m;
vector<int> v,vv[1<<N];
bool check(int x){
x|=(1<<m); //相当于在最后放一个竖着的挡板,防止横着的木块越界
for(int i=0;i<m;++i){
if( !(x&(1<<i)) &&(x&(1<<(i+1))) ) return false;
if( !(x&(1<<i)) &&!(x&(1<<(i+1))) ) i++;
}
return true;
}
int main(){
int T;
for(int i=0;i<(1<<N);++i){
v.push_back(i);
vv[i].clear();
}
for(int i=0;i<v.size();++i){
int x=v[i];
for(int j=0;j<v.size();++j){
int y=v[j];
if( !(x&y) ){
vv[i].push_back(y);
}
}
}
while(cin>>n>>m){
if(n==0&&m==0) break;
f[0][0]=1;
for(int i=1;i<=n;++i){
for(int j=0;j<v.size();++j){
int x=v[j];
if(x>=(1<<m)) break;
f[i][x]=0;
for(int k=0;k<vv[j].size();++k){
int y=vv[j][k];
if(y>=(1<<m)) break;
if(!check(x|y)) continue;
f[i][x]+=f[i-1][y];
}
}
}
cout<<f[n][0]<<endl;
}
return 0;
}
1064. 骑士
在 n×n 的棋盘上放 k 个国王,国王可攻击相邻的 8 个格子,求使它们无法互相攻击的方案总数。
输入格式
共一行,包含两个整数 n 和 k。
输出格式
共一行,表示方案总数,若不能够放置则输出0。
数据范围
1≤n≤10,
0≤k≤n2
输入样例:
3 2
输出样例:
16
思路: 首先预处理出来一行的合法状态:即不能有两个连续的骑士状态,再处理出来和每个状态合法的上一行状态:即两个状态不能在同一列以及相邻列都有骑士。
闫氏DP分析法:
#include<bits/stdc++.h>
using namespace std;
const int N=12;
long long f[N][N*N+10][1<<N];int ones[1<<N];
vector<int> v,vv[1<<N];
bool check(int x){
for(int i=0;i<N;++i){
if( (x&(1<<i))&&(x&(1<<(i+1))) ) return false;
}
return true;
}
int main(){
int n,m;
cin>>n>>m;
for(int i=0;i<(1<<n);++i){
for(int j=0;j<n;++j){
if((i&(1<<j))) ones[i]++;
}
}
for(int i=0;i<(1<<n);++i){
if(check(i)) v.push_back(i);
}
for(int i=0;i<v.size();++i){
for(int j=0;j<v.size();++j){
if(!(v[i]&v[j])&&check(v[i]|v[j])){
vv[i].push_back(v[j]);
}
}
}
f[0][0][0]=1;
for(int i=1;i<=n+1;++i){
for(int j=0;j<=m;++j){
for(int k=0;k<v.size();++k){
int x=v[k];
if(ones[x]>j) continue;
for(int l=0;l<vv[k].size();++l){
int y=vv[k][l];
f[i][j][x]+=f[i-1][j-ones[x]][y];
}
}
}
}
cout<<f[n+1][m][0]<<endl;
return 0;
}
327. 玉米田
农夫约翰的土地由M*N个小方格组成,现在他要在土地里种植玉米。
非常遗憾,部分土地是不育的,无法种植。
而且,相邻的土地不能同时种植玉米,也就是说种植玉米的所有方格之间都不会有公共边缘。
现在给定土地的大小,请你求出共有多少种种植方法。
土地上什么都不种也算一种方法。
输入格式
第1行包含两个整数M和N。
第2..M+1行:每行包含N个整数0或1,用来描述整个土地的状况,1表示该块土地肥沃,0表示该块土地不育。
输出格式
输出总种植方法对100000000取模后的值。
数据范围
1≤M,N≤12
输入样例:
2 3
1 1 1
0 1 0
输出样例:
9
思路: 预处理状态比上一题更简单一些,只是在枚举行的时候需要判断当前状态对于该行的土地是否合法,以及DP时不需要枚举个数
#include<bits/stdc++.h>
using namespace std;
const int N=14,mod=100000000;
vector<int> v,vv[1<<N];
long long f[N][1<<N],g[N];
bool check(int x){
for(int i=0;i<N;++i){
if( (x&(1<<i)) && (x&(1<<(i+1))) ) return false;
}
return true;
}
int main(){
int n,m;
cin>>n>>m;
for(int i=1;i<=n;++i){
int p=1,S;
for(int j=0;j<m;++j,p<<=1){
cin>>S;
g[i]+=p*S;
}
}
for(int i=0;i<(1<<m);++i){
if(check(i)){
v.push_back(i);
}
}
for(int i=0;i<v.size();++i){
for(int j=0;j<v.size();++j){
int x=v[i],y=v[j];
if(!(x&y)) {
vv[i].push_back(y);
}
}
}
f[0][0]=1;
for(int i=1;i<=n+1;++i){
for(int j=0;j<v.size();++j){
int x=v[j],y=g[i];
if((x&y)!=x) continue;
for(int k=0;k<vv[j].size();++k){
int z=vv[j][k],t=g[i-1];
if((z&t) !=z ) continue;
f[i][x]+=f[i-1][z];
f[i][x]%=mod;
}
}
}
cout<<f[n+1][0]<<endl;
return 0;
}
292. 炮兵阵地
司令部的将军们打算在NM的网格地图上部署他们的炮兵部队。一个NM的地图由N行M列组成,地图的每一格可能是山地(用”H” 表示),也可能是平原(用”P”表示),如下图。
在每一格平原地形上最多可以布置一支炮兵部队(山地上不能够部署炮兵部队);一支炮兵部队在地图上的攻击范围如图中黑色区域所示:
如果在地图中的灰色所标识的平原上部署一支炮兵部队,则图中的黑色的网格表示它能够攻击到的区域:沿横向左右各两格,沿纵向上下各两格。
图上其它白色网格均攻击不到。
从图上可见炮兵的攻击范围不受地形的影响。
现在,将军们规划如何部署炮兵部队,在防止误伤的前提下(保证任何两支炮兵部队之间不能互相攻击,即任何一支炮兵部队都不在其他支炮兵部队的攻击范围内),在整个地图区域内最多能够摆放多少我军的炮兵部队。
输入格式
第一行包含两个由空格分割开的正整数,分别表示N和M;
接下来的N行,每一行含有连续的M个字符(‘P’或者’H’),中间没有空格。按顺序表示地图中每一行的数据。
输出格式
仅一行,包含一个整数K,表示最多能摆放的炮兵部队的数量。
数据范围
N≤100,M≤10
输入样例:
5 4
PHPP
PPHH
PPPP
PHPP
PHHP
输出样例:
6
思路:
当前行的状态要受到前两行状态的影响,所以需要先处理行内不能有距离小于等于2的炮台,再出一行和它前两行的组合,需满足三行在某一列不能同时有炮台。
DP过程就是遍历每一行,先枚举当前行,在枚举前两行的组合,需要判断是否符合地图。这个复杂度看似有O(2^30),但其实可以枚举出来行内合法的状态大概只有100多,行组合也很少。
#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> PII;
const int N=12;
int f[2][(1<<N)][(1<<N)];
vector<PII> vp[1<<N];
vector<int> v;
bool check(int x){
for(int i=0;i<N;++i){
if((x&(1<<i)) && ((x&(1<<(i+1))) ||(x&(1<<(i+2))) ) ) return false;
}
return true;
}
char S[N];
int g[110],ones[1<<N];
int main(){
int n,m;
cin>>n>>m;
for(int i=1;i<=n;++i){
cin>>S;
int p=1;
for(int j=0;j<m;++j,p<<=1){
if(S[j]=='P') g[i]+=p;
}
}
for(int i=0;i<(1<<m);++i){
for(int j=0;j<30;++j){
if((i&(1<<j))) ones[i]++;
}
}
for(int i=0;i<(1<<m);++i){
if(check(i)){
v.push_back(i);
}
}
for(int i=0;i<(int)v.size();++i){
for(int j=0;j<(int)v.size();++j){
for(int k=0;k<(int)v.size();++k){
int x=v[i],y=v[j],z=v[k];
if((x&y)==0&&(x&z)==0&&(y&z)==0 ) {
vp[i].push_back({y,z});
}
}
}
}
f[0][0][0]=0;
int res=0;
for(int i=1;i<=n+2;++i){
for(int j=0;j<v.size();++j){
for(int k=0;k<vp[j].size();++k){
int x=v[j],y=vp[j][k].first,z=vp[j][k].second;
if(((x&g[i])!=x)||( i-1>0&&(y&g[i-1])!=y) ||(i-2>0&&(z&g[i-2])!=z) ) {
continue;
}
f[i%2][x][y]=max(f[(i-1)%2][y][z]+ones[x],f[i%2][x][y]);
}
}
}
cout<<f[(n+2)%2][0][0]<<endl;
return 0;
}