前置知识:2-SAT
题意
[XIX Open Cup, Grand Prix of Korea] Dev, Please Add This!
在一张网格上有以下几种物品:
- 空地(
.
) - 墙(
#
) - 星星(
*
) - 一个球(
O
)
现在你要玩一个游戏。你可以让球朝上下左右某一个方向移动,但是一旦移动就必须走到底,也就是说直到前面是墙或者边界才能停。另外,如果你走到了带有星星的格子上,你可以获得它。
你的任务是,判断一张网格是否有吃完所有星星的方案。
样例1
输入:
3 7
#..O..#
#.###.#
*..#..*
输出:
NO
不难发现没有两个星星都吃到的方案。因为一旦吃到一个星星就回不到起点了。
输入:
6 6
*..*##
..O...
*..*#.
####*.
......
.....#
输出:
YES
可以按照以下的步骤:右、下、左、下、右、上、右、上、左、上、右、下、左。
思路
首先,根据题目列出条件:
- 起点走不到的点不可能走到(合法性);
- 若两个位置不能相互可达,则这两个位置不可能都走到(合法性);
- 每个星星要么横着走到,要么竖着走到,不能不经过(目标)。
没了。显然,满足它们的路径存在,等价于有方案吃完所有星星。
那看到上面的加粗部分,就容易想到 2-SAT 了:
我们对所有「一段横向或纵向的极长可行走区间」编一个号作为图上的节点,u
表示该节点走,pre + u
表示该节点不走。然后从节点每个点跑一遍 dfs(或 bfs),令 vis[u][v]
表示可以从 u 到达 v。然后那么可以转为:
- 若
!vis[起点所属极长横区间][i] && !vis[起点所属极长纵区间][i]
,则走不到,连边i -> pre + i
; - 若
!vis[u][v] && !vis[v][u]
,则不能互相走到,连边u -> pre + v
、v -> pre + u
; - 若某一个格子是星星,令所属极长横区间的编号为
u
,所属极长纵区间的编号为v
,则连边pre + u -> v
、pre + v -> u
。
然后跑 2-SAT 即可。时间复杂度 \(\mathcal{O}(能过并且绰绰有余)\)。
代码
#include <bits/stdc++.h>
using namespace std;
const int N = 55, D = N * N * 2;
struct two_sat
{
int n;
vector<int> g[D];
void addEdge(int u, int v)
{
g[u].push_back(v);
}
int dfncnt, dfn[D], low[D], stk[D], tp;
bool instk[D];
int scccnt, id[D];
void tarjan(int u)
{
dfn[u] = low[u] = ++dfncnt;
stk[++tp] = u;
instk[u] = true;
for (int v : g[u])
{
if (!dfn[v])
{
tarjan(v);
low[u] = min(low[u], low[v]);
}
else if (instk[v])
low[u] = min(low[u], dfn[v]);
}
if (dfn[u] == low[u])
{
++scccnt;
int v;
do
{
v = stk[tp--];
instk[v] = false;
id[v] = scccnt;
} while (v != u);
}
}
} t;
int n, m, id[2][N][N], st;
// id[0][x][y]表示(x,y)所属的横向极长区间
// id[1][x][y]表示(x,y)所属的纵向极长区间
char s[N][N];
pair<int, int> l[D], r[D], S; // 每个极长区间的左右/上下端点
bool vis[D][D]; // vis[u][v]表示u能走到v
void dfs(int u)
{
vis[st][u] = true;
auto tmp = l[u];
int v = u ^ id[0][tmp.first][tmp.second] ^ id[1][tmp.first][tmp.second];
/*
上面那行代码等价于:
if (u == id[0][tmp.first][tmp.second])
v = id[1][tmp.first][tmp.second];
else
v = id[0][tmp.first][tmp.second];
下面同理。作用是分别从两个端点走,但要跟当前的方向不一样。
*/
if (!vis[st][v])
dfs(v);
tmp = r[u];
v = u ^ id[0][tmp.first][tmp.second] ^ id[1][tmp.first][tmp.second];
if (!vis[st][v])
dfs(v);
}
int main()
{
#ifdef aquazhao
freopen("data.in", "r", stdin);
freopen("data.out", "w", stdout);
#endif
cin >> n >> m;
for (int i = 1; i <= n; i++)
scanf("%s", s[i] + 1);
int cnt = 0;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
{
if (s[i][j] == '#')
continue;
if (j == 1 || s[i][j - 1] == '#')
l[++cnt] = {i, j};
id[0][i][j] = cnt;
if (j == m || s[i][j + 1] == '#')
r[cnt] = {i, j};
}
for (int j = 1; j <= m; j++)
for (int i = 1; i <= n; i++)
{
if (s[i][j] == '#')
continue;
if (i == 1 || s[i - 1][j] == '#')
l[++cnt] = {i, j};
id[1][i][j] = cnt;
if (i == n || s[i + 1][j] == '#')
r[cnt] = {i, j};
}
for (st = 1; st <= cnt; st++)
dfs(st);
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
{
if (s[i][j] == '*')
{
t.addEdge(id[0][i][j] + cnt, id[1][i][j]);
t.addEdge(id[1][i][j] + cnt, id[0][i][j]);
}
else if (s[i][j] == 'O')
S = {i, j};
}
for (int i = 1; i <= cnt; i++)
if (!vis[id[0][S.first][S.second]][i] && !vis[id[1][S.first][S.second]][i])
t.addEdge(i, i + cnt);
for (int i = 1; i <= cnt; i++)
for (int j = i + 1; j <= cnt; j++)
if (!vis[i][j] && !vis[j][i])
{
t.addEdge(i, j + cnt);
t.addEdge(j, i + cnt);
}
for (int i = 1; i <= cnt + cnt; i++)
if (!t.dfn[i])
t.tarjan(i);
bool ans = true;
for (int i = 1; i <= cnt; i++)
if (t.id[i] == t.id[i + cnt])
{
ans = false;
break;
}
puts(ans ? "YES" : "NO");
return 0;
}
标签:tmp,Korea,Cup,int,题解,vis,low,id,极长
From: https://www.cnblogs.com/avalaunch/p/18542515