Note -「拟阵交」& Solution -「CF 1284G」Seollal
Link.
给定张含空格和障碍格的 的地图。构造在四连通的空格中间放置墙壁的方案,使得:
- 所有空格在四连通意义下构成树;
- 除 外,所有满足 的空格 不是树的叶子。
,其实有多测,但根据数据规则可忽略。
草,这个 motivation 我实在编不下去了。总之看到 很小,而且是“双限制”的构造,也许你能想到拟阵交。
不妨称被钦定非叶的格子为黑格。发现直接翻译限制并不太拟阵,特别是“黑格不是叶子”,即黑格度数 这种限制。另一方面,若已经得到了一片满足黑格度数 的森林,在此基础上尝试让森林连通是非常简单的。因此,令 为所有可能存在的四连通关系,构造:
不难证明 和 是拟阵,取它们的交中最大的一个集合 。若 中仍有某个 ,根据 的最大性,一定不存在解。否则我们在 的基础上加入额外的边让生成森林连通即可。复杂度为 。(虽然这里摆个 显得很矫情,但我觉得是得有的嘛。)
然后是本题的主要矛盾:神 tm CF 摆道拟阵交,根本不会啊!
于是,这里记一下拟阵交算法。拟阵相关问题属于前置知识。
形式化地,对于定义在 上的 个拟阵 ,它们的交为
为什么记作 ?因为这一交的结果很可能不是拟阵。而对 的集族中最大集合的求解,就是所谓 拟阵交问题,不妨记作 。 可以描述为:输入 与整数 ,判断是否存在 。
当 时,喜闻乐见, 是 NPC 问题。
证明
当然,我们仅需证明 是 NPC 的。尝试将「二分图上的 Hamilton 路径问题」(已知的 NPC 问题)向其规约。
对于一个二分连通图 ,构造三个 上的集族
即图拟阵,亦不难证明 , 为拟阵。令 ,我们断言, 存在 Hamilton 回路,当且仅当 。
充分性: 显然是 Hamilton 路的边集。
必要性:Hamilton 路的边集显然一定是一个 。
注:我对 NP-complete,NP-hard 这些概念辨析暂时不深刻。若对此有质疑,麻烦参考一下其他资料 w。
不过,当 时,拟阵交是 P 的,下面来构建这个算法。算法的核心是,对于当前解 ,构造一个有向二分图——交换图 ,其中
算法的其他部分比较平凡。给出算法:
其中 为集合对称差,可以理解作集合异或。
设 ,不难得到该算法的复杂度为 ,当然这里假设“属于独立集”的判定是 的。
等等,这玩意儿为什么是对的呢?
证明
给出一个定理:
最大最小定理 对于上述的 ,有
我们指出,该算法所输出的 就是上式的一个 。以下同时证明这两件事。
首先, 是显然的,我们只需要证明不等式的取等。令 为上述算法的输出, 为 上所有可达 的点集。尝试证明:。
反证。若 ,则 ,使得 。另一方面,由于 不连通,所以 。注意到 ,所以 含有恰一个环 。如果 ,那么 ,这与其 矛盾。因此 。此时,。进一步地 ,则 ,矛盾。
同理。又由于
所以 取等啦。我们已经证明了最大最小定理。
此外,我们还需要证明 恒为独立集。这里先偷个懒,去读论文嘛。不过值得一提的是, 是最短路保证了 除起点外的 个点在二分图上有唯一完美匹配,因而才能保证 独立。
写的时候注意分清 ,一个集合到底需要在谁中的独立。调麻了 qwq。
/*+Rainybunny+*/
#include <bits/stdc++.h>
#define rep(i, l, r) for (int i = l, rep##i = r; i <= rep##i; ++i)
#define per(i, r, l) for (int i = r, per##i = l; i >= per##i; --i)
typedef std::pair<int, int> PII;
#define fi first
#define se second
const int MAXN = 20, IINF = 0x3f3f3f3f;
const int MOVE[4][2] = { { -1, 0 }, { 0, -1 }, { 1, 0 }, { 0, 1 } };
int n, m, deg[MAXN * MAXN + 5];
char maz[MAXN + 5][MAXN + 5], str[MAXN * 2 + 5][MAXN * 2 + 5];
inline bool inside(const int i, const int j) {
return 1 <= i && i <= n && 1 <= j && j <= m;
}
inline int id(const int i, const int j) {
return (i - 1) * m + j;
}
struct DSU {
int fa[MAXN * MAXN + 5], siz[MAXN * MAXN + 5];
inline void init() { rep (i, 1, n * m) siz[fa[i] = i] = 1; }
inline int find(const int x) {
return x == fa[x] ? x : fa[x] = find(fa[x]);
}
inline bool unite(int x, int y) {
if ((x = find(x)) == (y = find(y))) return false;
if (siz[x] < siz[y]) x ^= y ^= x ^= y;
return siz[fa[y] = x] += siz[y], true;
}
};
namespace MI { // Matroid Intersection.
std::vector<PII> U;
std::vector<int> ans, dis, L, R, T;
std::queue<int> que;
DSU dsu;
inline void initInd() {
dsu.init();
rep (i, 1, n) rep (j, 1, m) deg[id(i, j)] = (i + j) & 1 ? -IINF : 0;
deg[1] = -IINF;
rep (i, 0, int(U.size()) - 1) if (ans[i]) {
dsu.unite(U[i].fi, U[i].se), ++deg[U[i].fi], ++deg[U[i].se];
}
}
inline void build() {
initInd();
L.clear(), R.clear();
T.clear(), T.resize(U.size());
dis.clear(), dis.resize(U.size(), IINF);
rep (i, 0, int(U.size()) - 1) {
if (ans[i]) L.push_back(i);
else {
R.push_back(i);
if (deg[U[i].fi] < 2 && deg[U[i].se] < 2) T[i] = true;
if (dsu.find(U[i].fi) != dsu.find(U[i].se)) {
que.push(i), dis[i] = 0;
}
}
}
}
inline std::vector<int> augment() {
int fin = -1;
std::vector<int> pre(U.size(), -1);
while (!que.empty()) {
int u = que.front(); que.pop();
if (T[u]) { fin = u; break; }
if (ans[u]) {
dsu.init();
for (int x: L) if (x != u) dsu.unite(U[x].fi, U[x].se);
for (int v: R) {
if (dis[v] == IINF && dsu.find(U[v].fi) != dsu.find(U[v].se)) {
dis[v] = dis[u] + 1, pre[v] = u, que.push(v);
}
}
} else {
for (int v: L) if (dis[v] == IINF) {
++deg[U[u].fi], ++deg[U[u].se];
--deg[U[v].fi], --deg[U[v].se];
if (deg[U[u].fi] <= 2 && deg[U[u].se] <= 2
&& deg[U[v].fi] <= 2 && deg[U[v].se] <= 2) {
dis[v] = dis[u] + 1, pre[v] = u, que.push(v);
}
--deg[U[u].fi], --deg[U[u].se];
++deg[U[v].fi], ++deg[U[v].se];
}
}
}
if (!~fin) return {};
while (!que.empty()) que.pop();
std::vector<int> ret;
ret.push_back(fin);
while (~pre[fin]) ret.push_back(fin = pre[fin]);
return ret;
}
inline void solve() {
ans.clear(), ans.resize(U.size());
while (true) {
build();
auto&& res(augment());
if (res.empty()) break;
for (int id: res) ans[id] ^= 1;
}
}
} // namespace MI.
int main() {
int T; scanf("%d", &T);
while (T--) {
scanf("%d %d", &n, &m);
rep (i, 1, n) scanf("%s", maz[i] + 1);
MI::U.clear();
rep (i, 1, n) rep (j, 1, m) {
if (~(i + j) & 1 && id(i, j) != 1 && maz[i][j] == 'O') {
rep (k, 0, 3) {
int x = i + MOVE[k][0], y = j + MOVE[k][1];
if (inside(x, y) && maz[x][y] == 'O') {
MI::U.emplace_back(id(i, j), id(x, y));
}
}
}
}
MI::solve();
MI::initInd();
rep (i, 1, n) rep (j, 1, m) {
if (~(i + j) & 1 && id(i, j) != 1
&& maz[i][j] == 'O' && deg[id(i, j)] != 2) {
puts("NO"); goto FIN;
}
}
rep (i, 1, 2 * n - 1) {
rep (j, 1, 2 * m - 1) {
str[i][j] = i & 1 && j & 1 ? maz[i + 1 >> 1][j + 1 >> 1] : ' ';
}
str[i][2 * m] = '\0';
}
if (maz[1][2] == 'O') {
MI::U.emplace_back(id(1, 1), id(1, 2)), MI::ans.push_back(0);
}
if (maz[2][1] == 'O') {
MI::U.emplace_back(id(1, 1), id(2, 1)), MI::ans.push_back(0);
}
rep (i, 0, int(MI::U.size()) - 1) {
if (MI::dsu.find(MI::U[i].fi) != MI::dsu.find(MI::U[i].se)) {
MI::dsu.unite(MI::U[i].fi, MI::U[i].se);
MI::ans[i] = true;
}
}
rep (i, 1, n) rep (j, 1, m) {
if (maz[i][j] == 'O' && MI::dsu.find(id(i, j))
!= MI::dsu.find(id(1, 1))) {
puts("NO"); goto FIN;
}
}
rep (i, 0, int(MI::U.size()) - 1) if (MI::ans[i]) {
int b = (MI::U[i].fi - 1) % m + 1, a = (MI::U[i].fi - b) / m + 1;
int d = (MI::U[i].se - 1) % m + 1, c = (MI::U[i].se - d) / m + 1;
str[a + c - 1][b + d - 1] = 'O';
}
puts("YES");
rep (i, 1, 2 * n - 1) puts(str[i] + 1);
FIN: ;
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现