1. 程式人生 > >[POJ2411] [UImLocal2000] Mondriaan's Dream [狀態壓縮/輪廓線][dp]

[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})}}
具體證明我當然不會啦(
精度擺在那裡,這個公式算起來不是很準的