P5372 SNOI2019 积木
不难想到图论建模(也没啥别的思路了),考虑用一张图刻画网格板上的任意一种状态:
- 图有
个点,形成点阵,和网格板对应。 - 网格板上,一个积木对应一条边,积木占据的两个格子,对应这条边连接的两个点。
比如第一个样例中,起始时的网格板状态:
3 3
nnn
uuu
o<>
刻画为下面的图论模型:
我们试图发掘这样刻画出的图的性质。容易发现,这样刻画出的任何一种图,都是一个边数为
我们再试图刻画积木的移动。观察题目中的移动方式,形象地说,这种移动方式像是空位在它的上下左右四个方向中,选择一块毗邻的积木,然后把他“拽”下来。这种理解也恰好和方案的输出方式相吻合。而且,无论与空位毗邻的那个积木处于东西走向还是南北走向,它总是可以被移动。
于是可以发现,在图上,积木的移动可以刻画为:
- 在代表唯一空位的孤立点
的上下左右四个点中,选择一个点 。设 原来与 相连,即 原来是一块积木。 - 断开
,连接 ,代表 这块积木被拽到了 。 - 此时,
变成了新图上的空位。
下图是两个例子。
注意,上面只有点
是主动选择的,点 会被被动选择为与 相连接的点,它在 被选择后已经唯一。这里 不能随便选择一个与 相连接的点。比如下面红色的 选择就是不合法的,正确应该是绿色的 。
这里,我用另一种角度理解上面的刻画:先让空位
这种角度有助于理解多次连续的移动。事实上,多次连续的移动就是上面操作步骤的重复,
这里我再换一种角度理解加边 / 删边。我们将点阵看成一个完全的网格图,即任意上下相邻,左右相邻的两个点之间都有边,只不过边有虚实两种状态,如果一条边上有一个积木(也即原来对边的定义),则这条边是实边,否则这条边是虚边。这样以来原来的加边 / 删边就变成对边的实虚切换。
问题转化成,给定一个完全网格图,初始边集
- 路径起点为初始网格图中,唯一的不属于任意打开的边的点(也即唯一一个不在
中的点)。 - 一个点从路径起点出发,沿着路径走。每走一条边,这条边的虚实状态改变。
- 这条路径的第偶数步一定走的是一条实边。
- 沿着路径走完后,整张图边的实边集合变为
。
这里不用限制路径的第奇数步一定走虚边,因为每次第奇数步之前,
这里有一个小套路,对于有初始态和目标态开关切换问题,设
在本题中,目标态的要求就可以被刻画为:路径要满足
于是我们考察
如果一个点
- 如果
在 和 中,分别连出的实边不同(即连接的不是同一个点),则 中这两条实边都会保留, 在 中恰连着 条实边,度数为 。 - 如果
在 和 中,分别连出的实边相同(即连接的是同一个点),则 中这两条边都会消失,没有与 相关联的边。
然后分类讨论:
- 如果
和 中,那个没出现的点不同:设 为 中没出现的点,因为 在 中恰连着一条实边,因此 中这条实边将被保留, 的度数为 。同理, 的孤立点 的度数也为 。- 此时因为
中出现的点里,除了 和 度数为 以外其余度数均为 ,所以 由一堆环和 的一条链构成,并且这些链和环之间互不相交。
- 此时因为
- 如果
和 中,没出现的点相同:显然 中没有与这个孤立点相关联的边。- 此时因为
中出现的点的度数都是 ,所以 就是一堆互不相交的环的集合。 - 可以理解为上面那种情况中,因为
所以链消失的特殊情况。
- 此时因为
后面我们会证明一些性质,这个基本定义会多次用到,请留意:
本题中的
所以
。- 考虑
的定义, 不在 中,所以 ,所以 。
- 考虑
, …… 这条链是 、 相间的。- 根据
任意毗邻边不属于同一集合的性质。
- 根据
。- 考虑
的定义,原理同第一条。
- 考虑
的长度是偶数。- 根据上面三条可以推出。
然后考虑
那么
路径从
接下来考虑环必须走奇数次怎么处理。先来看某一个环怎么处理,如果某一个环可以处理,其它环也就能处理了。下称
对于多个环的情况,我们并不一定每次都要回到
这张图我们忽略原图上的点的方阵性,只是个示意图。
然后考虑偶实限制。这里我们做一个小限制:每次回溯时,从转折点出发找新的环,第一步必须是第奇数步,在找虚边。比如,上图中
另外,到达环上某个点时,因为这个环之前从来没走过,且环上的边都属于
又因为环长是偶数,所以如果最后导向这个环的那条边是路径的第奇数步,也即这条边原先虚,现在变实,那么在经过环上偶数条边,再从这条边返回的时候,它一定是要走第偶数步,因为这条边已经变实了,所以满足偶实。然后一路回溯的时候,恰好也都能吻合,如下图。
接下来就是最后一个问题了:为了保证任意一个环都能可达,需要保证从任意一个点出发,分别走虚边,实边,虚边……可达任意点。
证明我不会。参见一下 https://www.luogu.com.cn/discuss/621563 吧。
/*
* @Author: crab-in-the-northeast
* @Date: 2023-06-26 23:05:05
* @Last Modified by: crab-in-the-northeast
* @Last Modified time: 2023-06-26 23:05:05
*/
#include <bits/stdc++.h>
inline int read() {
int x = 0;
bool f = true;
char ch = getchar();
for (; !isdigit(ch); ch = getchar())
if (ch == '-')
f = false;
for (; isdigit(ch); ch = getchar())
x = (x << 1) + (x << 3) + ch - '0';
return f ? x : (~(x - 1));
}
inline char rech() {
char ch = getchar();
while (!isgraph(ch))
ch = getchar();
return ch;
}
const int N = 2003, M = 2003;
struct edge {
int v;
char dir;
inline bool exi() {
return this -> v != 0;
}
};
edge S[N * M], T[N * M];
namespace all {
edge G[N * M][4]; // 0 1 2 3 代表四个方向
}
namespace dif {
edge G[N * M][2]; // 0 虚 1 实
}
int n, m;
inline int id(int x, int y) {
if (x < 1 || x > n || y < 1 || y > m)
return 0;
return (x - 1) * m + y;
}
std :: bitset <N * M> vis, key;
// key:是否是 S 和 T 的对称差图中,有边链接的点。以确定我们是否走到环上
inline void step(int &u) {
key.reset(u);
putchar(dif :: G[u][0].dir);
u = dif :: G[u][0].v;
key.reset(u);
u = dif :: G[u][1].v;
key.reset(u);
}
inline void go(int u, int st) {
while (u != st)
step(u);
}
void dfs(int u) {
vis.set(u);
for (int d = 0; d < 4; ++d) {
edge e = all :: G[u][d];
if (!e.exi())
continue;
int v = e.v; char dir = e.dir;
if (vis[v])
continue;
if (key[v]) {
int s = dif :: G[v][1].v, t = dif :: G[v][0].v;
// u -> v -> s -> ... -> t -> v -> u
putchar(dir);
go(s, t);
putchar(dif :: G[t][0].dir);
key.reset(v);
}
vis.set(v);
v = T[v].v;
if (!T[v].exi() || vis[v])
continue;
putchar(dir);
if (key[v]) {
int now = v;
step(now);
go(now, v);
}
else
dfs(v);
putchar(T[v].dir);
}
}
int main() {
n = read(); m = read();
auto ed = [](int u, char dir) -> edge {
int i = (u - 1) / m + 1, j = (u - 1) % m + 1;
switch (dir) {
case 'U': return (edge){id(i - 1, j), 'U'};
case 'D': return (edge){id(i + 1, j), 'D'};
case 'L': return (edge){id(i, j - 1), 'L'};
case 'R': return (edge){id(i, j + 1), 'R'};
}
return (edge){0, 'N'};
};
int s = 0, t = 0;
for (int k = 0; k <= 1; ++k) {
for (int u = 1; u <= n * m; ++u) {
edge e = {0, 'N'};
switch (rech()) {
case '<': e = ed(u, 'R'); break;
case '>': e = ed(u, 'L'); break;
case 'n': e = ed(u, 'D'); break;
case 'u': e = ed(u, 'U'); break;
}
if (k) {
T[u] = e;
if (!e.v)
t = u;
} else {
S[u] = e;
if (!e.v)
s = u;
}
}
}
for (int u = 1; u <= n * m; ++u) {
all :: G[u][0] = ed(u, 'U');
all :: G[u][1] = ed(u, 'D');
all :: G[u][2] = ed(u, 'L');
all :: G[u][3] = ed(u, 'R');
if (S[u].dir != T[u].dir && S[u].exi() && T[u].exi()) {
int s = S[u].v, t = T[u].v;
dif :: G[u][0] = T[u];
dif :: G[t][0] = T[t];
dif :: G[u][1] = S[u];
dif :: G[s][1] = S[s];
key[u] = key[s] = key[t] = 1;
}
}
vis.set(t);
go(s, t);
dfs(t);
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效