\(\mathscr{Description}\)

  Link.

  给定张含空格和障碍格的 \(n\times m\) 的地图。构造在四连通的空格中间放置墙壁的方案,使得:

  • 所有空格在四连通意义下构成树;
  • 除 \((1,1)\) 外,所有满足 \(2\mid(i+j)\) 的空格 \((i,j)\) 不是树的叶子。

  \(n,m\le20\),其实有多测,但根据数据规则可忽略。

\(\mathscr{Solution}\)

  草,这个 motivation 我实在编不下去了。总之看到 \(n,m\) 很小,而且是“双限制”的构造,也许你能想到拟阵交。

  不妨称被钦定非叶的格子为黑格。发现直接翻译限制并不太拟阵,特别是“黑格不是叶子”,即黑格度数 \(\ge2\) 这种限制。另一方面,若已经得到了一片满足黑格度数 \(=2\) 的森林,在此基础上尝试让森林连通是非常简单的。因此,令 \(U\) 为所有可能存在的四连通关系,构造:

\[\mathcal M_1=(U,\mathcal I_1),~\mathcal I_1=\{S\subseteq U\mid \text{no circle in }G'=(V,S)\};\\
\mathcal M_2=(U,\mathcal I_2),~\mathcal I_2=\{S\subseteq U\mid \forall u\in V_{\text{black}},d_u\le 2\}.
\]

  不难证明 \(\mathcal M_1\) 和 \(\mathcal M_2\) 是拟阵,取它们的交中最大的一个集合 \(S\)。若 \(S\) 中仍有某个 \(u\in V_{\text{black}},d_u<2\),根据 \(|S|\) 的最大性,一定不存在解。否则我们在 \(S\) 的基础上加入额外的边让生成森林连通即可。复杂度为 \(\mathcal O((nm)^3\alpha(nm))\)。(虽然这里摆个 \(\alpha\) 显得很矫情,但我觉得是得有的嘛。)


  然后是本题的主要矛盾:神 tm CF 摆道拟阵交,根本不会啊!

  于是,这里记一下拟阵交算法。拟阵相关问题属于前置知识。

  形式化地,对于定义在 \(U\) 上的 \(k\) 个拟阵 \(\mathcal M_{1..k}\),它们的交为

\[\mathcal N=(U,\mathcal I_1\cap\cdots\cap\mathcal I_2).
\]

为什么记作 \(\mathcal N\)?因为这一交的结果很可能不是拟阵。而对 \(\mathcal N\) 的集族中最大集合的求解,就是所谓 \(k-\)拟阵交问题,不妨记作 \(k-\text{MI}\)。\(k-\text{MI}\) 可以描述为:输入 \(\mathcal M_{1..k}\) 与整数 \(n\),判断是否存在 \(|S|\ge n,S\in\mathcal N\)。

  当 \(k\ge3\) 时,喜闻乐见,\(k-\text{MI}\) 是 NPC 问题。

证明

  当然,我们仅需证明 \(3-\text{MI}\) 是 NPC 的。尝试将「二分图上的 Hamilton 路径问题」(已知的 NPC 问题)向其规约。

  对于一个二分连通图 \(G=(V=V^{\text L}\cup V^{\text R},E)\),构造三个 \(E\) 上的集族

\[\mathcal I_G=\{S\subseteq E\mid \text{no circle in }G'=(V,S)\},\\
\mathcal I_L=\{S\subseteq E\mid \forall u\in V_{G'}^{\text L},d_u\le 2\},\\
\mathcal I_R=\{S\subseteq E\mid \forall u\in V_{G'}^{\text R},d_u\le 2\}.
\]

\(\mathcal M_G=(E,\mathcal I_G)\) 即图拟阵,亦不难证明 \(\mathcal M_L=(E,\mathcal I_L)\),\(\mathcal M_R=(E,\mathcal I_R)\) 为拟阵。令 \(\mathcal N=(E,\mathcal I_G\cap\mathcal I_L\cap\mathcal I_R)\),我们断言,\(G\) 存在 Hamilton 回路,当且仅当 \(\exists S\in\mathcal N,|S|=|V|-1\)。

  充分性:\(S\) 显然是 Hamilton 路的边集。

  必要性:Hamilton 路的边集显然一定是一个 \(S\)。

$\square$

  注:我对 NP-complete,NP-hard 这些概念辨析暂时不深刻。若对此有质疑,麻烦参考一下其他资料 w。

  不过,当 \(k=2\) 时,拟阵交是 P 的,下面来构建这个算法。算法的核心是,对于当前解 \(I\),构造一个有向二分图——交换图 \(G_{\mathcal M_1,\mathcal M_2}(I)=(V=V^{\text{L}}\cup V^{\text{R}},E)\),其中

\[V^{\text{L}}=I,~V^{\text{R}}=U\setminus I;\\
\begin{aligned}
E&=\{\langle x,y\rangle\in V^{\text{L}}\times V^{\text{R}}\mid (I\setminus\{x\})\cup\{y\}\in\mathcal I_1\}\\
&\cup~\{\langle x,y\rangle\in V^{\text{R}}\times V^{\text{L}}\mid (I\setminus\{x\})\cup\{y\}\in\mathcal I_2\}.
\end{aligned}
\]

  算法的其他部分比较平凡。给出算法:

\[\begin{array}{}
\text{Algorithm: }2-\text{MI}.\\
\text{Input: two matroid }\mathcal M_1=(U,\mathcal I_1)\text{ and }\mathcal M_2=(U,\mathcal I_2).\\
\text{Output: an intersection }I\text{ of }\mathcal M_1\text{ and }\mathcal M_2\text{ of max size}.
\end{array}\\
\begin{array}{r|l}
1& I\leftarrow\varnothing\\
2& \textbf{repeat}:\\
3& \quad G\leftarrow G_{\mathcal M_1,\mathcal M_2}(I)\\
4& \quad X_1\leftarrow \{e\in U\setminus I\mid I\cup\{e\}\in\mathcal I_1\}\\
5& \quad X_2\leftarrow \{e\in U\setminus I\mid I\cup\{e\}\in\mathcal I_2\}\\
6& \quad P\leftarrow\text{the shortest path from }X_1\text{ to }X_2\text{ in }G\\
7& \quad I\leftarrow I~\Delta~P\\
8& \textbf{until }P\text{ does not exist.}\\
9& \textbf{return }I.
\end{array}
\]

其中 \(I~\Delta~P\) 为集合对称差,可以理解作集合异或。

  设 \(r=\min\{r_1(U),r_2(U)\}\),不难得到该算法的复杂度为 \(\mathcal O(r^2|U|)\),当然这里假设“属于独立集”的判定是 \(\mathcal O(1)\) 的。

  等等,这玩意儿为什么是对的呢?

证明

  给出一个定理:

  最大最小定理 对于上述的 \(\mathcal M_1,\mathcal M_2\),有

\[\max_{I\in\mathcal I_1\cap\mathcal I_2}\{|I|\}=\min_{S\subseteq U}\{r_1(S)+r_2(U\setminus S)\}.
\]

  我们指出,该算法所输出的 \(I\) 就是上式的一个 \(\arg\max\)。以下同时证明这两件事。

  首先,\(\text{LHS}\le\text{RHS}\) 是显然的,我们只需要证明不等式的取等。令 \(I\) 为上述算法的输出,\(S\) 为 \(G_{\mathcal M_1,\mathcal M_2}(I)\) 上所有可达 \(X_2\) 的点集。尝试证明:\(r_1(S)\le|I\cap S|\)。

  反证。若 \(r_1(S)>|I\cap S|\),则 \(\exists x\in S\setminus I\),使得 \((I\cap S)\cup\{x\}\in\mathcal I_1\)。另一方面,由于 \(X_1,X_2\) 不连通,所以 \(I\cup\{x\}\notin\mathcal I_1\)。注意到 \(I\in\mathcal I_1\),所以 \(I\cup\{x\}\) 含有恰一个环 \(C\)。如果 \(\forall y\in C\setminus\{x\},~y\in S\),那么 \(C\subseteq (I\cap S)\cup\{x\}\),这与其 \(\in\mathcal I_1\) 矛盾。因此 \(\exists y\in C,~y\notin S\)。此时,\((I\setminus\{y\})\cup\{x\}\in\mathcal I_1\)。进一步地 \(\lang y,x\rang\in E_G\),则 \(y\in S\),矛盾。

  \(r_2(U\setminus S)\le|I\cup(U\setminus S)|\) 同理。又由于

\[|I|=|I\cap S|+|I\cap(U\setminus S)|\le r_1(S)+r_2(U\setminus S),
\]

所以 \(I\) 取等啦。我们已经证明了最大最小定理。

  此外,我们还需要证明 \(I~\Delta~P\) 恒为独立集。这里先偷个懒,去读论文嘛。不过值得一提的是,\(P\) 是最短路保证了 \(P\) 除起点外的 \(|P|-1\) 个点在二分图上有唯一完美匹配,因而才能保证 \(I~\Delta~P\) 独立。

$\square$

  写的时候注意分清 \(\mathcal I_1,\mathcal I_2\),一个集合到底需要在谁中的独立。调麻了 qwq。

\(\mathscr{Code}\)

/*+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;
}

Note -「拟阵交」& Solution -「CF 1284G」Seollal的更多相关文章

  1. Solution -「CF 1342E」Placing Rooks

    \(\mathcal{Description}\)   Link.   在一个 \(n\times n\) 的国际象棋棋盘上摆 \(n\) 个车,求满足: 所有格子都可以被攻击到. 恰好存在 \(k\ ...

  2. Solution -「CTS 2019」「洛谷 P5404」氪金手游

    \(\mathcal{Description}\)   Link.   有 \(n\) 张卡牌,第 \(i\) 张的权值 \(w_i\in\{1,2,3\}\),且取值为 \(k\) 的概率正比于 \ ...

  3. Solution -「BZOJ 3812」主旋律

    \(\mathcal{Description}\)   Link.   给定含 \(n\) 个点 \(m\) 条边的简单有向图 \(G=(V,E)\),求 \(H=(V,E'\subseteq E)\ ...

  4. Solution -「GLR-R2」教材运送

    \(\mathcal{Description}\)   Link.   给定一棵包含 \(n\) 个点,有点权和边权的树.设当前位置 \(s\)(初始时 \(s=1\)),每次在 \(n\) 个结点内 ...

  5. Android内存管理(4)*官方教程 含「高效内存的16条策略」 Managing Your App's Memory

    Managing Your App's Memory In this document How Android Manages Memory Sharing Memory Allocating and ...

  6. Git 执行 「fork 出来的仓库」和「最新版本的原仓库」内容同步更新

    当我们在 GitHub 上 fork 出一个仓库后,如果原仓库更新了,此时怎样才能保证我们 fork 出来的仓库和原仓库内容一致呢?我们一般关注的是仓库的 master(主干分支)的内容,通过以下步骤 ...

  7. FileUpload控件「批次上传 / 多档案同时上传」的范例--以「流水号」产生「变量名称」

    原文出處  http://www.dotblogs.com.tw/mis2000lab/archive/2013/08/19/multiple_fileupload_asp_net_20130819. ...

  8. SSH连接时出现「WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!」解决办法

    用ssh來操控github,沒想到連線時,出現「WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!」,後面還有一大串英文,這時當然要向Google大神求助 ...

  9. Java的参数传递是「值传递」还是「引用传递」?

    关于Java传参时是引用传递还是值传递,一直是一个讨论比较多的话题. 有人说Java中只有值传递,也有人说值传递和引用传递都是存在的,比较容易让人产生疑问. 关于值传递和引用传递其实需要分情况看待. ...

  10. 「ZJOI2019」&「十二省联考 2019」题解索引

    「ZJOI2019」&「十二省联考 2019」题解索引 「ZJOI2019」 「ZJOI2019」线段树 「ZJOI2019」Minimax 搜索 「十二省联考 2019」 「十二省联考 20 ...

随机推荐

  1. PostgreSQL中将对象oid和对象名相互转换

    PostgreSQL中将对象oid转为对象名 使用pg的内部数据类型将对象oid转为对象名,可以简化一些系统视图的关联查询. 数据库类型转换对应类型的oid 可以用以下数据库类型转换对应类型的oid( ...

  2. 干货分享:Air780E软件指南:字符串处理

    一.Lua字符串介绍 关于字符串,Lua提供了一些灵活且强大的功能,一些入门知识如下: 1.1 字符串定义 在Lua中,字符串可以用单引号'或双引号"来定义.例如: localstr1='H ...

  3. Air780E软件指南:C语言内存数组(zbuff)

    一.ZBUFF(C内存数组)简介 zbuff库可以用c风格直接操作(下标从0开始),例如buff[0]=buff[3] 可以在sram上或者psram上申请空间,也可以自动申请(如存在psram则在p ...

  4. 2024-11-16:哈沙德数。用go语言,如果一个整数能够被它的各个数位上数字的和整除, 我们称这个整数为哈沙德数(Harshad number)。 给定一个整数 x, 如果 x 是哈沙德数,则返回

    2024-11-16:哈沙德数.用go语言,如果一个整数能够被它的各个数位上数字的和整除, 我们称这个整数为哈沙德数(Harshad number). 给定一个整数 x, 如果 x 是哈沙德数,则返回 ...

  5. orange pi 香橙派 zero 刷openwrt当作有wifi的小路由器用

    前面写过我用香橙派zero来测量温度 https://www.cnblogs.com/jar/p/15848178.html 最近准备把他改造成路由器 https://www.right.com.cn ...

  6. CF2027D The Endspeaker (Hard Version) 题解

    题面 给你一个长度为 \(n\) 的数组 \(a\) 和一个长度为 \(m\) 的数组 \(b\) (所有 \(1 \le i < m\) 满足 \(b_i > b_{i+1}\) ).最 ...

  7. 【一步步开发AI运动小程序】十七、如何识别用户上传视频中的人体、运动、动作、姿态?

    [云智AI运动识别小程序插件],可以为您的小程序,赋于人体检测识别.运动检测识别.姿态识别检测AI能力.本地原生识别引擎,内置10余个运动,无需依赖任何后台或第三方服务,有着识别速度快.体验佳.扩展性 ...

  8. json-lib(ezmorph)、gson、flexJson、fastjson、jackson对比,实现java转json,json转java

    json-lib(ezmorph).gson.flexJson.fastjson.jackson对比,实现java转json,json转java 本文中所讲的所有代码都在此:json-test 目前关 ...

  9. 优秀的 Java 程序员所应该知道的 Java 知识

    JDK 相关知识 JDK 的使用 JDK 源代码 JDK 相应技术背后的原理 JVM 相关知识 服务器端开发需要重点熟悉的 Java 技术 Java 并发 Java IO 开源框架 Java 之外的知 ...

  10. Python之时间日期操作

    常用时间操作的函数汇总, 涵盖 常用的time   datetime 1.计算两个日期相差天数 import datetime str1 = '2021-10-20' str2 = '2021-10- ...