状压dp

一、基本概念&算法基础:
顾名思义,就是把一些状态压缩成一个或多个n进制数来表示,然后通过数位的运算来判断这些数所对应的状态是否合法,从而成对状态的转移。
常用2进制,因此2进制位运算就很重要。

二、没了
注意合法性常常需要从多个方面考虑,不要漏掉需要考虑的情况。

例题:洛谷P1879 [USACO06NOV]Corn Fields G

码:

很容易想到把整张图表用每行一个的二进制数来表示
0表示荒废,1表示使用
#include<cstdio>
#include<algorithm>
using namespace std;
const int mod=1e8;
int n,m;
int a[13][13];
int F[13];//每行的初始输入状态
int f[13][1<<12+5];//dp用的转移数组,f[i][j]表示第i行状态为j时合法状态的总数
bool g[1<<12+5];//预处理普遍的状态合法性(不会有两个1相连)
int main()
{
	scanf("%d%d",&m,&n);
	for(int i=1;i<=m;++i)
	{
		for(int j=1;j<=n;++j)
		{
			scanf("%d",&a[i][j]);
			F[i]=(F[i]<<1)+a[i][j];//存储初始状态
		}
	}
	for(int i=0;i<(1<<n);++i)//处理出所有没有1相邻的情况
		g[i]=(!(i&(i<<1))) && (!(i&(i>>1)));//用左移和右移计算来完成判断:一个数向左移动1位,如果取&后的结果不为0,则说明有1相邻,右移同理。且如果只进行左移或者右移中的一种,显然会漏掉一些情况
	f[0][0]=1;//初始化
	for(int i=1;i<=m;++i)//枚举行数
		for(int j=0;j<(1<<n);++j)//枚举每行的状态
			if(g[j]&&(j&F[i])==j)//判断是否无1相邻且是否符合土地的贫富情况
				for(int k=0;k<(1<<n);k++)//枚举上一行的情况,如果想的话,可以把判断改成if((g[k])&&(k&j)==0),如果不加,反正也只是多加了一个0,不影响结果
					if((k&j)==0)
						f[i][j]=(f[i][j]+f[i-1][k])%mod;	
	int ans=0;
	for(int i=0;i<(1<<n);++i)
	{
		ans=(ans+f[m][i])%mod;	
	}//求和:由于最后一行每种情况独立,因此需要求和
	printf("%d",ans);
	return 0;
}

结语:状压dp这个东西,由于需要考虑的情况极多,因此复杂度也不会很优秀,仅略强于O(!n)的算法,因此,只有在n,m在10~20左右会被使用。
所以可以靠数据量猜算法,就跟猜测什么时候正解是暴搜一样

posted @ 2021-08-24 16:43  Mint-hexagram  阅读(33)  评论(0编辑  收藏  举报