状态压缩dp通常是将每一种状态转化为二进制数,从而枚举每一个二进制数,从而判断每一种状态。
Ⅰ:莫德里安的猜想:
在NM的棋盘分割成若干个12的长方形,有多少种方案
直接想的话,难以下手,所以我们考虑将每一列的状态转化为用01表示的,0表示上一列的这一行没有伸出一个小方格,1则表示上一行有伸出一个小方格
用
f
[
i
]
[
j
]
f[i][j]
f[i][j]存储状态:表示第
i
i
i列伸出了多少小方格,这个状态由
j
j
j用二进制存储,若有5行也就是
j
j
j的状态有0~31,(若第一行有其余都没有,那就是10000=16)即把整列的每一行的情况转化为二进制存储。
注意我们所说的1是上一列有伸出方块(横着放),也就是说当前列的当前行放有一个长方形的右端。0代表的是没有伸出方块,也就是上一列是竖着放的。一列中是不能出现奇数个0的(一个长方形为
1
×
2
1 \times 2
1×2竖着放长度为2,是偶数)。并且连续的两列中不能同一行出现两个1,因为这样就等于有两个长方形叠放了。
具体代码
#include<isotream>
using namespace std;
const int N = 12,M = 1 << N;
long long f[N][N];
bool st[N];
int main()
{
int n,m;
while(cin>>n>>m,n||m)
{
memset(f,0,sizeof f);
for(int i = 0; i < 1 << n; i ++ );//将每种放法都遍历一遍
{
st[i]=true;
int cnt=0;//记录连续0的个数
for(int j = 0; j < n; j ++ )//判断当前放法每一段有多少连续的0
{
if(i>>j&1)//判断是不是碰到1了
{
if(cnt&1) st[i]=false; //若是奇数0,就代表这种摆法是不合理的
cnt = 0;
}
else cnt++;//不是1的话就是0 那就将0的个数加1
}
if(cnt&1) st[i]=false;//因为可能最后一段是以0结尾的,这样最后一段就没办法用1作为结尾,
//所以我们要单独判断一下最后一段
}
f[0][0]=1;//因为第一列的上一列是不存在的,所以不可能有方块伸出,且只有全部都不放这一种方案。
for(int i = 1; i <= m; i ++ )//遍历每一列
for(int j = 0; j < 1 << n; j ++)//遍历这一列的每一种情况
for(int k = 0; k < 1 << n; k ++ )//遍历上一列的每一种情况
{
if((j&k) == 0 && st[j|k])//如果此时两者没有冲突 (每一位没有同时为1)
//st[j|k] 因为两者已经保证没有重叠了,然后该列是从上一列伸出来的,所有两者会有一块方块
//在同一列,竖着放的方块要根据这两者在同一列的位置判断,用按位或找出两者重合后还是空白的地方,判断有没有奇数空白
//若是奇数就代表不合法,因此我们前面的预处理就起了作用
f[i][j]+=f[i-1][k];
}
cout << f[m][0] << endl;//最后的答案就是m+1列没有任何方块伸出来的情况
}
return 0;
}
Ⅱ:最短Hamilton路径
给定
n
n
n个点的带权无向图,无重边,点从
0
∼
n
−
1
0 \sim n-1
0∼n−1标号,求起点0到终点n-1的最短Hamilton路径(从
0
∼
n
−
1
0\sim n-1
0∼n−1不重不漏地经过每个点恰好一次)
一样是用状态压缩来实现,用一个整数来表示一个状态
f
[
i
]
[
j
]
f[i][j]
f[i][j]:所有从
0
0
0走到
j
j
j,走过的点是
i
i
i这个整数代表的所有点的所有路径的最小值,
i
i
i是一个二进制数。
求最小值
分类的话用走过的路径的倒数第二个点来分类
假如我们走到
j
j
j,上一个走过的点是
k
k
k,那么
k
−
j
k-j
k−j的路径长度是固定的,所以就可以直接求
f
[
i
−
{
j
}
]
[
k
]
+
a
[
k
]
[
j
]
f[i-\{j\}][k]+a[k][j]
f[i−{j}][k]+a[k][j]的min
,
i
−
{
j
}
,i-\{j\}
,i−{j}表示将
j
j
j这个位变为0,表示还未走过。
具体代码
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=20,M=1<<N;//M表示状态数
int n;
int w[N][N];//边权
int f[M][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;//0号点为起点,并且只有0号点走过了。所以距离为0.
for(int i=0;i<1<<n;i++)//遍历状态
for(int j=0;j<n;j++)//遍历结尾点
{
if(i>>j&1)//如果j是走过的,因为j为结尾,所以这个状态下,j要是走过的
{
for(int k=0;k<n;k++)//遍历上一个点
{
if(i-(1<<j)>>k&1)//如果k不是与j为同一个点,并且k也是走过的
{
f[i][j]=min(f[i][j],f[i-(1<<j)][k]+w[k][j]);//取最小值
}
}
}
}
cout<<f[(1<<n)-1][n-1]<<endl;//输出所有点都经过并且结尾为n-1号点的最小值。
}
i-(1<<j)>>k&1 注意这个代码的理解
Ⅲ:骑士
来自一本通
题目:在
n
×
n
n \times n
n×n 的棋盘上放
k
k
k 个国王,国王可攻击相邻的
8
8
8 个格子,求使它们无法互相攻击的方案总数。
题意:每个国王的攻击范围是与他周围的一圈格子,也就是一个’井‘。并且每一行哪些位置可以放国王,只与上一行的国王放置有关。所以我们只需要判断两行之间的关系就可以了。
首先我们看本行,一个放置方式如果是合法的,那么就不能有连续的两个国王。
接着我们看两行的关系,也就是两行之间任意连续的两列内不能有超过1个国王。
我们将这些情况转化为用01表示的状态。1表示这个位置有国王,0表示没有。
所以上述的两个限制条件可以转化为
bool check(int state)
{
for(int i=0;i<n;i++)
{
if((state>>i&1)&&(state>>i+1&1))//若有连续的两个0
return false;
}
return true;
}
check(a)&&check(b)&&(a&b)==0&&check(a|b)//ab表示两行的状态
状态数组
f
[
i
]
[
j
]
[
k
]
f[i][j][k]
f[i][j][k]:表示现在判断到第
i
i
i行,已经安置了
j
j
j个国王,当前行的状态为
k
k
k
状态转移方程为
f[i][j][a]+=f[i-1][j-c][b]//c表示a状态下的国王数
具体代码
#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
#define int long long
using namespace std;
const int N=12,M=1<<10,k=110;
int n,m;
vector<int>state;//记录所有合法的状态,就是没有连续的1
vector<int>head[M];//记录可以转移到当前状态的所有状态
int cnt[M];//记录每个合法状态中骑士的数量
int f[N][k][M];
bool check(int state)
{
for(int i=0;i<n;i++)
{
if((state>>i&1)&&(state>>i+1&1))//若有连续的两个0
return false;
}
return true;
}
int count(int state)
{
int res=0;
for(int i=0;i<n;i++) res+=state>>i&1;//计算1的数量
return res;
}
signed main()
{
cin>>n>>m;
for(int i=0;i<1<<n;i++)//枚举所有可能的状态,寻找合法的
{
if(check(i))
{
state.push_back(i);//加入合法序列
cnt[i]=count(i);//记录这个合法状态中1的数量
}
}
for(int i=0;i<state.size();i++)
{
for(int j=0;j<state.size();j++)
{
int a=state[i],b=state[j];
if((a&b)==0&&check(a|b))
{
head[i].push_back(j);
}
}
}
f[0][0][0]=1;
for(int i=1;i<=n+1;i++)
{
for(int j=0;j<=m;j++)
{
for(int a=0;a<state.size();a++)
{
for(int b:head[a])
{
int c=cnt[state[a]];
if(j>=c)
{
f[i][j][a]+=f[i-1][j-c][b];
}
}
}
}
}
cout<<f[n+1][m][0]<<endl;
return 0;
}