状态压缩DP
状态压缩DP是通过二进制压缩状态的一种DP思路,位运算在状压DP中非常常用,状态压缩的题目的数据范围会很小,有些情况下,暴力算法也可以通过
例题1:玉米地
题目:
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.
输入说明:
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)
输出说明:
Line 1: One integer: the number of ways that FJ can choose the squares modulo 100,000,000.
分析:
状态表示: 我们用f[i] [j]表示第i行采用第j个方案,前i行所有的方案数
方案表示: 我们采用二进制数来表示方案,二进制1表示种玉米,0表示不种
方案存储: 我们用一个state数组记录所有可行的方案,同时统计方案数
常用位运算: (i>>j&1)表示二进制i的第j位是不是1
状态转移: 状态压缩DP通常考虑倒数第二层的情况,来递推情况,我们这里可以写出状态转移方程:
注意,这里要判断上一层的选法是否会与该层的选法出现冲突违规的现象
AC代码:
#include<iostream>
using namespace std;
int m,n;
int s[1<<15]; //这个数组用来存储每行的情况,同样以二进制表示,1表示能种玉米,0表示不能
int f[15][1<<15];
int state[1<<15],cnt;
const int MOD=1e9;
bool check(int x){ //判断是否有相邻的1
if(x&(x<<1))return false;
return true;
}
int main(){
cin>>m>>n;
for(int i=0;i<1<<n;i++){
if(check(i)){
state[cnt++]=i; //记录所有合理的状态
}
}
for(int i=0;i<m;i++){
for(int j=0;j<n;j++){
int x;cin>>x;
if(x==0)s[i] |= (1<<j);//记录是否有1
}
}
//初始化第一行
for(int i=0;i<cnt;i++){
if(!(s[0]&state[i])){ //没有冲突就初始化为1
f[0][i]=1;
}
}
for(int i=1;i<m;i++){
for(int j=0;j<cnt;j++){ //state[j]代表第i行的方案
if(s[i]&state[j])continue;//有冲突就不再计算
//状态转移考虑第i-1行采用了何种方案,注意判断冲突与否
for(int k=0;k<cnt;k++){
if(s[i-1]&state[k]||state[j]&state[k])continue;
f[i][j]=(f[i][j]+f[i-1][k])%MOD;
}
}
}
int res=0;
//最后计算第m-1行可行方案数的总和
for(int i=0;i<cnt;++i){
res=(res+f[m-1][i])%MOD;
}
cout<<res<<endl;
return 0;
}
例题2:最短哈密顿路径
分析:
状态表示: 用f[i] [j]表示所有从0走到j,途中经过二进制数i记录的点的所有路径
我们要求解的是最小值
状态初始化: 所有f[i] [j] 都初始化成一个大值,同时f[1] [0]=0
状态转移: 同样,考虑没走到 j 之前的情况,我们仍然去枚举走到 j 之前走的所有可能点k,我们写出如下的状态转移方程:
AC代码:
#include<iostream>
#include<cstring>
using namespace std;
const int N=20,M=1<<N;
int n;
int f[M][N];
int w[N][N]; //这个用来记录所给出的邻接矩阵
int main(){
cin>>n;
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
cin>>w[i][j];
}
}
memset(f,0x3f,sizeof f);
f[1][0]=0;
for(int i=0;i<1<<n;i++){
for(int j=0;j<n;j++){
if(i>>j&1){ //只有该位为1才走
for(int k=0;k<n;k++){
if(i>>k&1){
f[i][j]=min(f[i][j],f[i-(1<<j)][k]+w[k][j]);
//这里的i-(1<<j)表示去除第j位的情况
}
}
}
}
}
cout<<f[(1<<n)-1][n-1]<<endl; //最后返回走到n-1,并且所有点状态都是1的f就是答案
return 0;
}