\(\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. Java 面向对象高级

    文章目录 1.静态 1.1 static修饰成员变量 1.2 static修饰成员变量的应用场景 1.3 static修饰成员方法 1.4 工具类 1.5 static的注意事项 1.6 static ...

  2. TP6 使用 nusoap为第三方webservice调用插件

    composer下载插件 composer require nusoap/nusoap use NuSoap\Client\Client; class Index extends BaseContro ...

  3. 22.使用Rancher2.0搭建Kubernetes集群

    使用Rancher2.0搭建Kubernetes集群 中文文档:https://docs.rancher.cn/docs/rancher2 安装Rancher2.0 使用下面命令,我们快速的安装 # ...

  4. vagrant 环境安装(前置篇)

    ubuntu可以直接 apt 源查找 sudo apt search vagrant 直接就 sudo apt-get install vagrant 如果版本不是 2.2.6 可以去 https:/ ...

  5. (Redis基础教程之十一) 如何使Redis中的Key过期

    介绍 Redis是一个开源的内存中键值数据存储.默认情况下,Redis密钥是_永久性_的,这意味着Redis服务器将继续存储它们,除非手动将其删除.但是,在某些情况下,您已经设置了密钥,但是您知道要在 ...

  6. golang之gRPC

    相关链接: grpc: https://grpc.io/docs/languages/go/quickstart/ protobuf: https://protobuf.dev/programming ...

  7. ubuntu 下的 nslookup 命令利用 127.0.0.53 查询主机名失败,而使用网关则正常的问题

    遇到一个奇怪的问题,ubuntu 下使用 KRDC 远程访问局域网主机时,连接主机名失败,使用 ip 则正常.通过 nslookup 命令发现,局域网主机名没有被正确解析(使用的是默认的 127.0. ...

  8. elastic8.4.0搜索+logstash<=>mysql实时同步+kibana可视化操作+netcore代码笔记

    做全文搜索,es比较好用,安装可能有点费时费力.mysql安装就不说了.主要是elastic8.4.0+kibana8.4.0+logstash-8.16.1,可视化操作及少了netcore查询代码. ...

  9. Python 学习记录(1)

    前言 简单说明: 简单情况:主要记录学习Python的简单情况,包括代码与结果,以及关键注释 工具与来源: 以下代码与结果都可在JupyterLab上实现,更多情况可看Github 使用 NumPy ...

  10. ZCMU-1120

    就这样 #include<cmath> #include<cstdio> #include<iostream> using namespace std; int m ...