[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]\)的条件是
- \(j\&k=0\),保证\(1\)的下方为\(0\);
- \(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)\)。转移有三种情况:
- \((i-1,j)\)未覆盖,只能竖着填;
- \((i,j-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, 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 by 2inf

浙公网安备 33010602011771号