题解
使用插头dp求解 hash表+位运算加快存取速度 使用滚动hash表作为dp数组 hash表存储轮廓线上的"路径插头"对应的方案数量
分类讨论每个格子的情况 情况太多不想重新写了 具体看代码。。备注很详细
我的菜比代码没看懂的可以看某高中大佬blog
AC代码
#include <stdio.h>
#include <unordered_map>
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
const int MAXN = 20;
const int HASZ = 1e5 + 3;
int n, m, ex, ey; //最后一个有效格子
char g[MAXN][MAXN];
struct HashTable //利用hash表存储状态 遍历不需要find
{
struct node
{
ll key, val;
int nxt;
}elem[HASZ]; //存储元素
int head[HASZ], tot; //head存储每个位置的第一个元素编号
void Init()
{
tot = 0;
memset(head, -1, sizeof(head));
}
void Push(ll key, ll val)
{
int k = key % HASZ; //取模位置
for (int i = head[k]; ~i; i = elem[i].nxt)
if (elem[i].key == key) //key相等
{
elem[i].val += val; //已有+val
return;
}
elem[tot].key = key, elem[tot].val = val, elem[tot].nxt = head[k]; //将原有接在后面
head[k] = tot++; //head记录当前
}
}d[2]; //0无 1左括号 2右括号
inline int getbit(ll s, int p) //获取状态s第p位的值
{
return (s >> (p - 1 << 1)) & 3;
}
inline void setbit(ll &s, int p, int v) //将状态s第p位设置为v
{
s = s & ~(3 << (p - 1 << 1)) | (v << (p - 1 << 1));
}
int main()
{
#ifdef LOCAL
freopen("C:/input.txt", "r", stdin);
#endif
cin >> n >> m;
for (int i = 1; i <= n; i++)
{
scanf("%s", g[i] + 1);
for (int j = 1; j <= m; j++)
if (g[i][j] == '.')
ex = i, ey = j;
}
if (!ex && !ey) //没有有效格子
cout << 0 << endl, exit(0);
d[1].Init();
d[1].Push(0, 1); //最初什么都没有为1个答案
int p = 0; //滚动数组 当前状态
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++) //逐格递推
{
d[p].Init(); //清空当前状态
for (int k = 0; k < d[!p].tot; k++) //遍历上个状态
{
ll s = d[!p].elem[k].key, v = d[!p].elem[k].val; //状态 数量
int l = getbit(s, j), u = getbit(s, j + 1); //获取左和上的插头状态
if (g[i][j] == '*') //当前为墙
{
if (!l && !u) //左上都没插头才合法
d[p].Push(s, v); //新的状态加继承原状态数量
}
else //不是墙
{
if (!l && !u) //都没有插头
{
if (g[i][j + 1] == '.' && g[i + 1][j] == '.') //下方和右侧都不是墙
setbit(s, j, 1), setbit(s, j + 1, 2), d[p].Push(s, v); //创建两个插头并配对 ┌
}
else if (!l || !u) //有一个没有
{
if (g[i][j + 1] == '.') //右侧不是墙 向右延伸 ─└
setbit(s, j, 0), setbit(s, j + 1, l + u), d[p].Push(s, v); //保持原有插头类型
if (g[i + 1][j] == '.') //向下 ┐│
setbit(s, j, l + u), setbit(s, j + 1, 0), d[p].Push(s, v);
}
else //都有插头
{
if (l == 1 && u == 2) //左侧是左括号 右侧是右括号则说明回路终点
{
if (i == ex && j == ey) //是最后一个格子才为合法状态
setbit(s, j, 0), setbit(s, j + 1, 0), d[p].Push(s, v); //清除插头
}
else if (l == 2 && u == 1) //左侧是一个联通分量 右侧是一个联通分量
setbit(s, j, 0), setbit(s, j + 1, 0), d[p].Push(s, v); //直接清除插头并合并
else //两个同侧括号
{
int cnt = 0; //相同括号
for (int e = j + (l == 1); e >= 1 && e <= m + 1; e += (l == 1 ? 1 : -1)) //同右向左 同左向右
{
int b = getbit(s, e);
if (b == l) cnt++;
else if (b) cnt--;
if (!cnt) //找到配对的
{
setbit(s, e, l); //将括号设置为和当前同向
setbit(s, j, 0), setbit(s, j + 1, 0); //并将当前两个括号清除
d[p].Push(s, v);
break;
}
}
}
}
}
}
p = !p; //切换数组
}
for (int j = 0; j < d[!p].tot; j++) //将状态全部左移2位为下一行做准备 ──┘-->┌──
d[!p].elem[j].key <<= 2;
}
ll ans = 0;
for (int i = 0; i < d[!p].tot; i++) //将最后一个状态的数量求和
ans += d[!p].elem[i].val;
cout << ans << endl;
return 0;
}