蜗牛

一步一步往上爬

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

状态压缩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;
}
posted on 2021-10-08 09:32  对影丶成三人  阅读(32)  评论(0)    收藏  举报