状态压缩DP
用二进制表示状态,方便位运算
蒙德里安的梦想
先找横着放的,横着的方案确定了,竖着的是唯一的
求把 N×M 的棋盘分割成若干个 1×2 的的长方形,有多少种方案。
例如当 N=2,M=4 时,共有 5 种方案。当 N=2,M=3 时,共有 3 种方案。
如下图所示:
2411_1.jpg
输入格式
输入包含多组测试用例。
每组测试用例占一行,包含两个整数 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-1列摆好且从第i-1列伸出到第i列的状态为j的所有方案的数量
#include<bits/stdc++.h>
using namespace std;
const int N = 12, M = 1<<N;
long long f[N][M];
//存储当前状态空出的连续位置是否为偶数
bool st[M];
//共m行的变长数组 记录合法状态
vector<vector<int>> state(M);
int n, m;
int main()
{
while(cin>>n>>m, n||m){
// 一个小优化,先预处理记录所有空着的长度都是偶数的位置
// 因为状态对应的是列,枚举当前表示的列的每一行
for( int i = 0; i < 1<<n; i++ ){
// cnt记录连续空的位置的数量
int cnt = 0;
bool is_vaild = true;
for( int j = 0; j < n; j++ ){
// 如果当前位置是1
if(i>>j&1){
// 看一下连续的空位的数量 如果是奇数
if(cnt&1){
// 不合法 此种状态不用
is_vaild = false;
cnt = 0;
break;
}
}
else
cnt++;
}
// 判断一下最后的空位的数量
if(cnt&1)
is_vaild = false;
// 记录当前这种状态是否有效
st[i] = is_vaild;
}
// 预处理每一种状态的合法性
// -> j和k(当前列和当前列的前一列)不能在同一行同时为1
// 枚举当前列的所有状态
for( int j = 0; j < 1<<n; j++ ){
// 多组输入,保证不会被污染
state[j].clear();
// 枚举前一列的状态
for( int k = 0; k < 1<<n; k++ )
// 如果这两列不在同一行同时为1且当前状态产生的空为偶数
if((j&k)==0 && st[j|k])
// 记录前一列的状态
state[j].push_back(k);
}
// 防污染
memset(f, 0, sizeof f);
// 不放也是一种方案,边界问题
f[0][0] = 1;
// 枚举每一列,实际看的是 0-(i-1)
for( int i = 1; i <= m; i++ )
// 枚举当前列的所有状态
for( int j = 0; j < 1<<n; j++ )
// 取出所有合法的状态
for( auto k: state[j])
// 状态转移 当前列 = 前一列每种状态的所有方案之和
f[i][j]+=f[i-1][k];
// 前m-1列(就是整个棋盘)已经摆好且从第m-1列没有伸出的所有方案
cout<<f[m][0]<<endl;
}
return 0;
}
最短Hamilton路径
给定一张 n 个点的带权无向图,点从 0∼n−1 标号,求起点 0 到终点 n−1 的最短 Hamilton 路径。
Hamilton 路径的定义是从 0 到 n−1 不重不漏地经过每个点恰好一次。
输入格式
第一行输入整数 n。
接下来 n 行每行 n 个整数,其中第 i 行第 j 个整数表示点 i 到 j 的距离(记为 a[i,j])。
对于任意的 x,y,z,数据保证 a[x,x]=0,a[x,y]=a[y,x] 并且 a[x,y]+a[y,z]≥a[x,z]。
输出格式
输出一个整数,表示最短 Hamilton 路径的长度。
数据范围
1≤n≤20
0≤a[i,j]≤107
输入样例:
5
0 2 4 5 1
2 0 6 5 3
4 6 0 8 3
5 5 8 0 5
1 3 3 5 0
输出样例:
18
//我们再计算两点之间的路径时,并不关心经过的点的顺序,
//只需要两点路径经过的点集和当前的“终点”
//f[i][j]:所有从0走到j,走过的点的状态是i的所有路径的集合
#include<bits/stdc++.h>
using namespace std;
const int N = 20, M = 1<<N;
int n;
int f[M][N], 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)
// 走到j点之前,以k点为终点的最短距离
for( int k = 0; k < n; k++ )
// 如果当前状态要经过k点
if(i>>k&1)
// 到k的距离加上k到j的权值
// 走到k的路径不能经过j
f[i][j] = min(f[i][j], f[i-(1<<j)][k]+w[k][j]);
// 所有点都走过了,终点是n-1的最短距离
cout<<f[(1<<n)-1][n-1]<<endl;
return 0;
}
本文来自博客园,作者:对影丶成三人,转载请注明原文链接:https://www.cnblogs.com/IntroductionToAlgorithms/p/15378320.html
浙公网安备 33010602011771号