[POJ2411] [UImLocal2000] Mondriaan's Dream [状态压缩/轮廓线][dp]

[ L i n k \frak{Link} ]


这道题属于棋盘覆盖问题的变形。
这一类问题在固定行数/列数的情况下是一类十分经典的构造dp;
不论行数/列数,这类问题一般都有明显的轮廓线概念。
同时在数据范围进一步扩大的情况下、可能还可以用矩阵来优化转移。
关于棋盘覆盖问题也有组合学公式,不过不会要求掌握。

这道题在(单纯的)状态压缩和轮廓线的意义下解法是不同的,并且本题的轮廓线解法优于一般的状态压缩。
建议在大约掌握状态压缩dp的一般解题方法之后写这道题目入门轮廓线。


状态压缩

 很容易想到压缩行的状态进行转移。
 每行都要填满,所以记录上一行状态是没有用的,要记录上一行覆盖了这一行哪些地方。
 然后枚举这一行的可行状态,更新。

 实际上状态里面记录的是上一行没有覆盖这一行的哪些地方
 因为这样比起记录覆盖,可以更简单地通过位运算判可行。

 注意枚举状态时候还是要枚举 0 \frak{0} L i m \frak{Lim} 而不能枚举可行状态

Accepted 863b
G++ 63ms 812k
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<ctime>
#include<cstdlib>
using namespace std;
int N,M;
long long F[12][2050]={};
bool C[2050]={};
bool check(int x)
{
	int t=0;
	while(x)
	{
		if(x&1)
		{
			++t;
		}
		else
		{
			if(t&1)return 0;
			t=0;
		}
		x>>=1;
	}
	if(t&1)return 0;
	return 1;
}
int main()
{
	for(int i=0;i<(1<<11);++i)if(check(i))C[i]=1;
	while(~scanf("%d%d",&N,&M))
	{
		if(!(N|M))return 0;
		if(N<M)swap(N,M);
		if((N*M)&1)
		{
			printf("0\n");
			continue;
		}
		memset(F,0,sizeof(F));
		for(int i=0;i<(1<<M);++i)if(check(i))F[1][i]=1;
		for(int i=1;i<N;++i)
		{
			for(int j=0;j<(1<<M);++j)
			{
				for(int k=0;k<(1<<M);++k)
				{
					if((j|k)!=((1<<M)-1))continue;
					if(!C[j&k])continue;
					F[i+1][k]+=F[i][j];
				}
			}
		}
		printf("%lld\n",F[N][(1<<M)-1]);
	}
	return 0;
}

轮廓线

 按行转移会产生许多无效的重复计算。
 考虑逐格转移。

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<cctype>
#include<ctime>
#include<cstdlib>
using namespace std;
int n,m;
bool p;
long long F[2][2052]={};
int main()
{
	while(~scanf("%d%d",&n,&m))
	{
		if(!n)return 0;
		if((n*m)&1)
		{
			printf("0\n");
			continue;
		}
		if(n<m)swap(n,m);
		memset(F[0],0,sizeof(F[0 ]));
		p=0; F[0][0]=1;
		for(int i=0;i<n;++i)
		{
			for(int j=0;j<m;++j)
			{
				p=!p;
				memset(F[p],0,sizeof(F[p]));
				for(int S=0;S<(1<<m);++S)
				{
					if(!F[!p][S])continue;
					F[p][S^(1<<j)]+=F[!p][S];
					if(j&&(S&(1<<j-1))&&(!(S&(1<<j))))F[p][S^(1<<j-1)]+=F[!p][S];
				}
			}
		}
		printf("%lld\n",F[p][0]);
	}
    return 0;
}


数学公式

a n s = 2 n m 2 i = 1 n j = 1 m c o s 2 ( i π n + 1 ) + c o s 2 ( j π m + 1 ) 4 \frak{ans=2^{\frac{nm}{2}}\prod\limits_{i=1}^{n}\prod\limits_{j=1}^m\sqrt[4]{cos^2(\frac{i\pi}{n+1})+cos^2(\frac{j\pi}{m+1})}}
具体证明我当然不会啦(
精度摆在那里,这个公式算起来不是很准的

猜你喜欢

转载自blog.csdn.net/Estia_/article/details/83351189