【luogu P1879】Corn Fields G / 玉米田++ / 玉米田(加加强版)(状压DP)(轮廓线DP)
Corn Fields G / 玉米田++ / 玉米田(加加强版)
题目链接:luogu P1879
题目大意
给你一个 n*m 的矩阵,有一些位置可以选放不放东西。
然后规定一个东西旁边四个位置不能有东西。
问你有多少种放的方案。
思路
首先不会加强版的自己先回去看看我的加强版,本文是在那个的基础上继续搞,所以不会讲思路,而是讲如何优化。
——>加强版做法<——
然后我们发现第一个问题是空间,会爆。
你直接一个 \(f\) 数组就爆了。
但你仔细想想会发现有一些状态根本就是不合法的。
那你轮廓线只会有一个断点,那一个顶多就会有一个地方连起来。
那如果有多个地方连起来,就说明这个状态就是不合法的。
你可以把合法的找出来重新编号,经我的测试(不一定准),最多不超过 \(13\) 万。
你就搞定了空间。
然后你枚举也只用枚举这些位置,然后你会发现你时间也可以了。
。。。
然后虽然大数据在本地跑要 2s,甚至会 5s,但它会自动给你开 O2,在 OJ 上还是能过的。
然后就好啦。
代码
#include<cstdio>
#include<cstring>
#define mo 100000000
using namespace std;
bool a[121][21];
int n, m, now, re;
int f[2][130001], ans, tot, num;
int to[2097152], fr[130001], yes;
char c;
int read() {
re = 0;
c = getchar();
while (c < '0' || c > '9') c = getchar();
while (c >= '0' && c <= '9') {
re = (re << 3) + (re << 1) + c - '0';
c = getchar();
}
return re;
}
int main() {
// freopen("cowfood.in", "r", stdin);
// freopen("cowfood.out", "w", stdout);
n = read(); m = read();
for (int i = 1; i <= n; i++)
for (int j = 0; j < m; j++)
a[i][j] = read();
memset(to, -1, sizeof(to));//把有用的状态找出来
for (int i = 0; i < (1 << m); i++) {
num = 0; yes = 2;
for (int j = 0; j < m; j++)
if ((i >> j) & 1) {
num++;
if (num == 2 && yes == 2) {
yes = 1;
num = 1;
}
else if (num == 2 && yes == 1) {
yes = 0;
break;
}
}
else num = 0;
if (yes) {
to[i] = tot++;
fr[tot - 1] = i;
}
}
f[0][0] = 1; now = 0;
for (int i = 1; i <= n; i++)
for (int j = 0; j < m; j++) {
now ^= 1;
for (int k = 0; k < tot; k++) f[now][k] = 0;
for (int k = 0; k < tot; k++) {
int up = (1 << j) & fr[k], lft = (j == 0 ? 0 : (1 << (j - 1)) & fr[k]);
if (i == 1 && up) continue;
if (j == 0 && lft) continue;
if (up) {
if (to[fr[k] ^ (1 << j)] == -1) continue;
f[now][to[fr[k] ^ (1 << j)]] += f[now ^ 1][k];
if (f[now][to[fr[k] ^ (1 << j)]] > mo) f[now][to[fr[k] ^ (1 << j)]] -= mo;
continue;
}
if (lft || !a[i][j]) {
f[now][k] += f[now ^ 1][k];
if (f[now][k] > mo) f[now][k] -= mo;
continue;
}
f[now][k] += f[now ^ 1][k];
if (f[now][k] > mo) f[now][k] -= mo;
if (to[fr[k] ^ (1 << j)] == -1) continue;
f[now][to[fr[k] ^ (1 << j)]] += f[now ^ 1][k];
if (f[now][to[fr[k] ^ (1 << j)]] > mo) f[now][to[fr[k] ^ (1 << j)]] -= mo;
}
}
for (int i = 0; i < tot; i++)
ans = (ans + f[now][i]) % mo;
printf("%d", ans);
fclose(stdin);
fclose(stdout);
return 0;
}