[GDKOI2016]地图 题解
[GDKOI2016]地图 题解
比模板简单的插头DP,算重调了好久 /kk
算法标签
插头DP, 状压DP,枚举
题面
给一个 \(n \times m,(n , m \le 7)\) 的网格图,有六种元素:
.:空地#:障碍物?:未知S:入口X:神器E:出口
试 ? 可为其他任意元素,求有多少种合法的网格。
一张网格称为合法当且仅当存在 S ,X ,E 各一个,且三者互相联通
问题分析
一看 \(n\) 的范围,再看考察连通性,直接插头 DP
前言
感谢巨佬 \(\text{B} \color{red}{\text{ooksnow}}\) 的激情指导和谆谆教诲!!!/bx
实现细节
首先说一下我的错误解法(调了好久)
这道题是求格子之间的联通,而不是路径,所以我们很自然地可以想到[JLOI2009]神秘的生物这到题
用 Hash 表维护 \(m\) 个插头,分别表示这些格子所在连通块的编号
由于 \(m \le 7\) ,所以最多有 \(4\) 个不同的连通块同时存在
刚刚好卡 \(4\) 进制状压 /fn,于是只能 \(8\) 进制
再维护三个 bool 型变量 \(f1, f2, f3\) ,分别表示当前已选要称为最后的连通块中的格子中,是否出现过 S ,X ,E
对于确定格,直接转移,否则枚举可能的情况分别转移
转移时,设 \(p\) 和 \(q\) 分别表示左插头和上插头
-
\(p = 0, q = 0\)
不选,插头状态不变
选,新建一个插头,但要判断当前是否为特殊格
-
\(p \not = 0, q = 0\)
不选,插头状态不变
选,当前格子的插头变为 \(p\),同时判断特殊格
-
\(p = 0, q \not = 0\)
先看是否只有一个 \(q\) 插头,是当前格就必须选(因为不选的话 \(q\) 就不会进入最后的联通块内;
否则可以不选,删去上插头
选,插头状态不变,同时判断特殊格
-
\(p \not = 0, q \not = 0\)
同样,先看 \(q\) 插头的个数,考虑能否不选
选,如果 \(p \not = q\) ,则要将 \(p\) ,\(q\) 合并为一种插头,同时判断特殊格
这样做只需在每次求最小表示法时统计连通块个数,如果只有一个连通块了,并且 \(f1 = f2 = f3 = 1\) ,就贡献答案
这样做是会算重的 /kk
因为在一个 ? 格时,会枚举 # ,此时已经算了一种当前格不选的情况
而又在枚举 . 的情况时,又会考虑不算当前格,这显然与上面的情况时一样的
而且这道题并没要求最后一定所有的非 # 都在一个连通块内
所以我们可以直接规定 # 不选,非 # 必选,同时 \(f1, f2, f3\) 分别维护 S ,X ,E 所在连通块的编号
最后只需遍历 Hash 表,如果当前状态 \(f1 = f2 = f3 \not = 0\) ,则加上贡献
不要忘记取模!
至于为什么会产生上面的错解呢,其实是被 [JLOI2009]神秘的生物 带偏了 /kk
代码实现
我的代码是直接将 \(f1, f2, f3\) 压在插头状态的最后三位,因为不想写结构体(
#include <bits/stdc++.h>
using namespace std;
#define re register
#define i64 long long
// #define pair pair<int, int>
// #define File(a) freopen(a".in", "r", stdin), freopen(a".out", "w", stdout);
#define getchar() (p1 == p2 && (p2 = (p1 = buf) + fread (buf, 1, 1 << 21, stdin), p1 == p2) ? EOF : *p1++)
char buf[1 << 21], *p1 = buf, *p2 = buf;
inline int read()
{
int x = 0, f = 0;
char c = getchar();
while (!isdigit(c)) {if (c == '-') f = 1;c = getchar();}
while (isdigit(c)) {x = (x << 3) + (x << 1) + c - 48;c = getchar();}
return f ? -x : x;
}
inline string getstr()
{
string res = "";
char ch = getchar();
while (isspace(ch)) ch = getchar();
while (!isspace(ch)) res.push_back(ch), ch = getchar();
return res;
}
const int N = 12, M = 5e5 + 3, inf = 0x3f3f3f3f, P = 1e9 + 7;
const int HS = 99991;
int n, m, now, las = 1;
char a[N][N];
i64 mi[N], to[2][M], ans;
int first[HS + 2], nex[2][M], w[2][M], num[2];
inline void Insert(i64 v, int val)
{
int u = v % HS + 1;
for (re int i = first[u]; i; i = nex[now][i])
{
int vv = to[now][i];
if (vv == v) return w[now][i] = (w[now][i] + val) % P, void();
}
nex[now][++num[now]] = first[u];
first[u] = num[now];
to[now][num[now]] = v;
w[now][num[now]] = val;
}
inline char getch()
{
char ch = getchar();
while (isspace(ch)) ch = getchar();
return ch;
}
int id[N];
inline int MP(int u, int val, int j)
{
int cnt = 0; i64 res = 0;
for (re int i = 1; i <= 7; ++i) id[i] = 0;
for (re int i = 0; i < m + 3; ++i)
{
int p = (u >> i * 3) % 8;
if (!p) continue;
if (!id[p]) id[p] = ++cnt;
res += id[p] * mi[i];
}
return res;
}
inline void trans(char x, i64 u, int val, int p, int q, int j)
{
if (!p && !q)
{
if (x == '#') Insert(MP(u, val, j), val);
else
{
u += mi[j - 1] * 7;
if (x == 'S') u += mi[m] * 7;
else if (x == 'X') u += mi[m + 1] * 7;
else if (x == 'E') u += mi[m + 2] * 7;
Insert(MP(u, val, j), val);
}
}
else if (p && !q)
{
if (x == '#') Insert(MP(u, val, j), val);
else
{
u += p * mi[j - 1];
if (x == 'S') u += mi[m] * p;
else if (x == 'X') u += mi[m + 1] * p;
else if (x == 'E') u += mi[m + 2] * p;
Insert(MP(u, val, j), val);
}
}
else if (!p && q)
{
if (x == '#') Insert(MP(u - q * mi[j - 1], val, j), val);
else
{
if (x == 'S') u += mi[m] * q;
else if (x == 'X') u += mi[m + 1] * q;
else if (x == 'E') u += mi[m + 2] * q;
Insert(MP(u, val, j), val);
}
}
else if (p && q)
{
if (x == '#') Insert(MP(u - q * mi[j - 1], val, j), val);
else
{
if (p != q)
{
for (re int jj = 0; jj < m + 3; ++jj)
if ((u >> jj * 3) % 8 == q)
u += p * mi[jj] - q * mi[jj];
}
if (x == 'S') u += mi[m] * p;
else if (x == 'X') u += mi[m + 1] * p;
else if (x == 'E') u += mi[m + 2] * p;
Insert(MP(u, val, j), val);
}
}
}
signed main()
{
bool f1 = 0, f2 = 0, f3 = 0;
n = read(), m = read();
for (re int i = 1; i <= n; ++i)
for (re int j = 1; j <= m; ++j)
{
a[i][j] = getch();
if (a[i][j] == 'S') f1 = 1;
else if (a[i][j] == 'X') f2 = 1;
else if (a[i][j] == 'E') f3 = 1;
}
w[now][++num[now]] = 1, mi[0] = 1;
for (re int i = 1; i <= 11; ++i) mi[i] = mi[i - 1] << 3;
for (re int i = 1; i <= n; ++i)
{
for (re int j = 1; j <= m; ++j)
{
memset(first, 0, sizeof first);
swap(las, now), num[now] = 0;
for (re int k = 1; k <= num[las]; ++k)
{
int u = to[las][k]; int val = w[las][k];
int p = (u >> (j - 2) * 3) % 8, q = (u >> (j - 1) * 3) % 8;
if (j == 1) p = 0;
if (a[i][j] == '?')
{
if (!f1 && !((u >> m * 3) % 8)) trans('S', u, val, p, q, j);
if (!f2 && !((u >> (m + 1) * 3) % 8)) trans('X', u, val, p, q, j);
if (!f3 && !((u >> (m + 2) * 3) % 8)) trans('E', u, val, p, q, j);
trans('.', u, val, p, q, j);
trans('#', u, val, p, q, j);
}
else trans(a[i][j], u, val, p, q, j);
}
}
}
for (re int i = 1; i <= num[now]; ++i)
{
i64 u = to[now][i]; int val = w[now][i];
int f1 = (u >> m * 3) % 8, f2 = (u >> (m + 1) * 3) % 8, f3 = (u >> (m + 2) * 3) % 8;
if (f1 == f2 && f1 == f3 && f1) ans += val, ans %= P;
}
printf("%lld", ans);
return 0;
}
总结
- 插头DP的类型并不多,多练多写自然提高正确率
- 具体题目具体分析

该文不被密码保护。
浙公网安备 33010602011771号