DP状态压缩

本文介绍了两个动态规划的应用实例。第一个问题是蒙德里安的梦想,通过动态规划求解棋盘上颜色块排列的合法方案数。核心策略是先考虑横着放置,然后推导出竖直放置的方案。第二个问题是最短Hamilton路径,利用动态规划寻找图中不重复经过所有点的最短路径。关键在于状态转移,确保路径不重复且最短。
摘要由CSDN通过智能技术生成

291. 蒙德里安的梦想

题意:

 思路:核心:先横着放,再竖着放。(所以只需要考虑横着放的方案就可,因为横着的确定了,竖着的也就确定了)。
[i][j]表示从第i-1列伸出到第i列状态为j的方案。

#include<iostream>
#include<vector>
#include<cstring>
using namespace std;
typedef long long ll;
const int N=15,M=1<<N;
ll f[N][M];
bool st[M];//记录状态是否合法
vector<int> state[M];//记录和状态m是合法的所有状态
int n,m;
int main()
{
	while(cin>>n>>m,n||m)
	{
		for(int i=0;i<1<<n;i++)//n行,所以有2^n种排放的可能
		{
			int cnt=0;//记录连续0的个数
			bool is_valid=true;
			for(int j=0;j<n;j++)//遍历每一列,一列n行
			{
				if(i>>j&1)//当前位为1
				{
					if(cnt&1) //连续的0的个数为奇数
					{
						is_valid=false;
						break;
					}
				}
				else cnt++;
			}
			if(cnt&1) is_valid=false;//判断最后一段0的奇偶性
			st[i]=is_valid;//记录状态i是否是合法的
		}
		for(int i=0;i<1<<n;i++)//枚举第i列的所有状态
		{
			state[i].clear();
			for(int j=0;j<1<<n;j++)//第i-2列伸到i-1列的状态
			{
				if((i&j)==0&&st[i|j])//i&j,从第i-1列伸到i列的状态与第i-2列伸到第i-1列的状态要不冲突
					state[i].push_back(j);//st[i|j]第i-1列自己的状态
			}
		}
			memset(f,0,sizeof(f));
		f[0][0]=1;//从第-1列伸到0列的状态,只有一种
		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];
		}
		printf("%lld\n",f[m][0]);//表示没有从m-1列伸到第m列的了(我们是从第0列开始的,第m列是再棋盘外的了),即整个棋盘都处理完了。
	}
	return 0;
}

AcWing 91. 最短Hamilton路径
题意:从点0到点n不重不漏经过所有点的最短路径长度。
思路:关键点①哪些点被用过;②当前停在哪个点。
f[i][j]表示停在j点状态为i,可转移为f[i][j]=f[state][k]+w[k][j](有一条从k-j的路),state是不包含j点的集合。有n个点,于是有2^n种状态,枚举所有状态,判断在每个点i时当前状态是否包含了这个点i,包含的话,枚举其他点,其他点所在的状态不能包含当前点i。

#include<iostream>
#include<cstring>
using namespace std;
const int N=20,M=1<<N;
int f[M][N],w[N][N];
int 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这个点,状态为1,距离为0
	for(int i=0;i<1<<n;i++)//枚举所有状态
		for(int j=0;j<n;j++)//枚举每个点
		{
			if(i>>j&1)//这个状态包含j这个点的话
			{
				for(int k=0;k<n;k++)
				{
					if((i-(1<<j))>>k&1)//i-(1<<j)这个状态不包含j,i-(1<<j)>>k&1这个状态包含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这个点
	return 0;
}


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值