[GDKOI2016]地图 题解

[GDKOI2016]地图 题解

比模板简单的插头DP,算重调了好久 /kk

算法标签

插头DP状压DP枚举

题面

给一个 \(n \times m,(n , m \le 7)\) 的网格图,有六种元素:

  1. . :空地
  2. # :障碍物
  3. ? :未知
  4. S :入口
  5. X :神器
  6. E :出口

? 可为其他任意元素,求有多少种合法的网格。

一张网格称为合法当且仅当存在 SXE 各一个,且三者互相联通

问题分析

一看 \(n\) 的范围,再看考察连通性,直接插头 DP

前言

感谢巨佬 \(\text{B} \color{red}{\text{ooksnow}}\) 的激情指导和谆谆教诲!!!/bx

实现细节

首先说一下我的错误解法(调了好久)

这道题是求格子之间的联通,而不是路径,所以我们很自然地可以想到[JLOI2009]神秘的生物这到题

用 Hash 表维护 \(m\) 个插头,分别表示这些格子所在连通块的编号

由于 \(m \le 7\) ,所以最多有 \(4\) 个不同的连通块同时存在

刚刚好卡 \(4\) 进制状压 /fn,于是只能 \(8\) 进制

再维护三个 bool 型变量 \(f1, f2, f3\) ,分别表示当前已选要称为最后的连通块中的格子中,是否出现过 SXE

对于确定格,直接转移,否则枚举可能的情况分别转移

转移时,设 \(p\)\(q\) 分别表示左插头和上插头

  1. \(p = 0, q = 0\)

    不选,插头状态不变

    选,新建一个插头,但要判断当前是否为特殊格

  2. \(p \not = 0, q = 0\)

    不选,插头状态不变

    选,当前格子的插头变为 \(p\),同时判断特殊格

  3. \(p = 0, q \not = 0\)

    先看是否只有一个 \(q\) 插头,是当前格就必须选(因为不选的话 \(q\) 就不会进入最后的联通块内;

    否则可以不选,删去上插头

    选,插头状态不变,同时判断特殊格

  4. \(p \not = 0, q \not = 0\)

    同样,先看 \(q\) 插头的个数,考虑能否不选

    选,如果 \(p \not = q\) ,则要将 \(p\)\(q\) 合并为一种插头,同时判断特殊格

这样做只需在每次求最小表示法时统计连通块个数,如果只有一个连通块了,并且 \(f1 = f2 = f3 = 1\) ,就贡献答案

这样做是会算重的 /kk

因为在一个 ? 格时,会枚举 # ,此时已经算了一种当前格不选的情况

而又在枚举 . 的情况时,又会考虑不算当前格,这显然与上面的情况时一样的

而且这道题并没要求最后一定所有的非 # 都在一个连通块内

所以我们可以直接规定 # 不选,非 # 必选,同时 \(f1, f2, f3\) 分别维护 SXE 所在连通块的编号

最后只需遍历 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;
}

总结

  1. 插头DP的类型并不多,多练多写自然提高正确率
  2. 具体题目具体分析
posted @ 2022-05-06 21:37  After-glow  阅读(38)  评论(0)    收藏  举报