[POJ2411] [UImLocal2000] Mondriaan's Dream [狀態壓縮/輪廓線][dp]
阿新 • • 發佈:2018-10-31
這道題屬於棋盤覆蓋問題的變形。
這一類問題在固定行數/列數的情況下是一類十分經典的構造dp;
不論行數/列數,這類問題一般都有明顯的輪廓線概念。
同時在資料範圍進一步擴大的情況下、可能還可以用矩陣來優化轉移。
關於棋盤覆蓋問題也有組合學公式,不過不會要求掌握。
這道題在(單純的)狀態壓縮和輪廓線的意義下解法是不同的,並且本題的輪廓線解法優於一般的狀態壓縮。
建議在大約掌握狀態壓縮dp的一般解題方法之後寫這道題目入門輪廓線。
狀態壓縮
很容易想到壓縮行的狀態進行轉移。
每行都要填滿,所以記錄上一行狀態是沒有用的,要記錄上一行覆蓋了這一行哪些地方。
然後列舉這一行的可行狀態,更新。
實際上狀態裡面記錄的是上一行沒有覆蓋這一行的哪些地方
因為這樣比起記錄覆蓋,可以更簡單地通過位運算判可行。
注意列舉狀態時候還是要列舉 到 而不能列舉可行狀態
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;
}
數學公式
具體證明我當然不會啦(
精度擺在那裡,這個公式算起來不是很準的