[poj2411] Mondriaan's Dream

题目链接

题意

  给定\(h\times w\ (1\leq h,w\leq11)\)的矩形,用\(1\times 2\)的小矩形去填充,问有多少种填充方式。

法一

  \(h,w\)较小,直接递推,\(f[i][j]\)表示第\(i\)行状态为\(j\)的方法数,初值\(f[0][2^w-1]=1\),目标为\(f[h][2^w-1]\)

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
using namespace std;

#define debug(x) cout << #x << " is " << x << endl
#define inc(i, a, b) for (int i = a; i <= b; ++i)
typedef long long ll;
const int INF = 2e9;

int h, w, cnt;
ll f[12][1<<11];
ll path[11*(1<<12)][2];

void dfs(int c, int pre, int now) {
	if (c == w) {
		path[++cnt][0] = pre;
		path[cnt][1] = now;
		return;
	}
	dfs(c + 1, pre<<1 | 1, now<<1);
	dfs(c + 1, pre<<1, now<<1 | 1);
	if (c < w - 1) dfs(c + 2, pre<<2 | 3, now<<2 | 3);
}

int main() 
{
	while (scanf("%d%d", &h, &w) && (h | w)) {
		if (h < w) swap(h, w);
		cnt = 0;
		dfs(0, 0, 0);
		memset(f, 0, sizeof(f));
		f[0][(1<<w)-1] = 1;
		for (int i = 1; i <= h; ++i)
			for (int j = 1; j <= cnt; ++j)
				f[i][path[j][1]] += f[i-1][path[j][0]];
		printf("%lld\n", f[h][(1<<w)-1]);
	}
    return 0;
}

法二

  考虑状压DP,时间复杂度\(O(2^{2w}h)\)。处理行时,用\(w\)位二进制数,第\(k\)位为1表示第\(k\)列为一个竖着的矩形的上半部分,第\(k\)位为0表示其他情况。设\(f[i][j]\)表示第\(i\)行形态为\(j\)\(j\)为用十进制数记录的\(w\)位二进制数)时,前\(i\)行分割方案的总数,初值\(f[0][0]=1\),目标为\(f[h][0]\)\(f[i-1][k]\to f[i][j]\)的条件是

  1. \(j\&k=0\),保证\(1\)的下方为\(0\)
  2. \(j|k\in S\)\(S\)记录满足二进制表示每一段连续的\(0\)有偶数个的整数),这些\(0\)表示横着的矩形。

从而得状态转移方程

\[f[i][j]=\sum_{j\&k=0\ and\ j|k\in S}f[i-1][k] \]

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
using namespace std;

#define debug(x) cout << #x << " is " << x << endl
#define inc(i, a, b) for (int i = a; i <= b; ++i)
typedef long long ll;
const int INF = 2e9;

int h, w;
ll f[12][1<<11];
bool ins[1<<11];

int main() 
{
	while (scanf("%d%d", &h, &w) && (h | w)) {
		for (int i = 0; i < 1 << w; ++i) {
			bool cnt = 0, flag = 0;
			for (int j = 0; j < w; ++j)
				if (i >> j & 1) flag |= cnt, cnt = 0;
				else cnt ^= 1;
			ins[i] = flag | cnt ? 0 : 1;
		}
		f[0][0] = 1;
		for (int i = 1; i <= h; ++i) {
			for (int j = 0; j < 1 << w; ++j) {
				f[i][j] = 0;
				for (int k = 0; k < 1 << w; ++k) {
					if (((j&k) == 0) && ins[j|k])
						f[i][j] += f[i-1][k];
				}
			}
		}
		printf("%lld\n", f[h][0]);
	}
    return 0;
}

法三

  轮廓线DP,按格进行转移,记0为已覆盖,1为未覆盖,采用二维滚动数组,用\(k\)表示当前格前\(w\)格的状态,\(f(i,j,k)\)表示\(i\)\(j\)列前\(w\)格填法为\(k\)的方案数,初始\(f(0,0,0)=1\),目标为\(f(h,0,0)\)。转移有三种情况:

  1. \((i-1,j)\)未覆盖,只能竖着填;
  2. \((i,j-1)\)未覆盖,只能横着填;
  3. 不填,等后面格子来填。

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
using namespace std;

#define debug(x) cout << #x << " is " << x << endl
#define inc(i, a, b) for (int i = a; i <= b; ++i)
typedef long long ll;
const int INF = 2e9;

int h, w, bit[20];
ll f[2][1<<11];

int main() 
{
	bit[0] = 1; for (int i = 1; i <= 15; ++i) bit[i] = bit[i-1] << 1;
	while (scanf("%d%d", &h, &w) && (h | w)) {
		memset(f, 0, sizeof(f));
		int cur = 0;
		f[0][0] = 1;
		for (int i = 0; i < h; ++i) {
			for (int j = 0; j < w; ++j) {
				cur ^= 1; // cur = (i * h + j + 1) % 2;
				memset(f[cur], 0, sizeof(f[cur]));
				for (int k = 0; k < bit[w]; ++k)
					if (f[cur^1][k]) {
						int left = j ? k>>(j-1)&1 : 0, up = k>>j&1;
						if (up) f[cur][k^bit[j]] += f[cur^1][k];
						else {
							if (left) f[cur][k^bit[j-1]] += f[cur^1][k];
							f[cur][k^bit[j]] += f[cur^1][k];
						}
					}
			}
		}
		printf("%lld\n", f[cur][0]);
	}
    return 0;
}

法四

  一般地,\(2\times1\)骨牌覆盖\(n\times m\)棋盘有公式

\[f(n,m)=\begin{cases}\displaystyle\prod_{i=1}^{\lfloor\frac{n}{2}\rfloor}\prod_{j=1}^{\lfloor\frac{m}{2}\rfloor}\left(4\cos^2{\frac{\pi i}{n+1}}+4\cos^2{\frac{\pi j}{m+1}}\right)&(n\ is\ even,\ or\ m\ is\ even.)\\ 0&(n\ is\ odd,\ and\ m\ is\ odd.)\end{cases}\]

所以可以直接利用公式计算,但需要注意精度。

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
using namespace std;

#define debug(x) cout << #x << " is " << x << endl
#define inc(i, a, b) for (int i = a; i <= b; ++i)
typedef long long ll;
const int INF = 2e9;
const double PI = 3.1415926535897932;

int main() 
{
	int h, w;
	double ans;
	while (scanf("%d%d", &h, &w) && (h | w)) {
		ans = !((h & 1) & (w & 1));
		for (int i = 1; 2 * i <= h; ++i)
			for (int j = 1; 2 * j <= w; ++j)
				ans *= 4*pow(cos(i*PI/(h+1)), 2) + 4*pow(cos(j*PI/(w+1)), 2);
		printf("%lld\n", (ll)(ans + 0.5));
	}
    return 0;
}
posted @ 2020-04-26 18:32  2inf  阅读(129)  评论(0)    收藏  举报