\(\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. python项目实战——人生重开模拟器

    文章目录 1.菜单栏的编写 2.玩家确定颜值.体质.智力.家境 3.生成性别 4.设定角色出生点 5.各个年龄段的变化 5.1 幼年阶段 5.2 青年阶段 5.3中年阶段 5.4 晚年阶段 6.整体代 ...

  2. DRF-Parser解析器组件源码分析和应用

    1. 解析器源码分析 注意:以下源码为了方便理解已进行简化,只保留了解析器相关的代码 # 视图函数: class MyView(APIView): def post(self, request): p ...

  3. typescript 编译报错 不能用于索引类型

    Element implicitly has an 'any' type because expression of type 'any' can't be used to index type 't ...

  4. NOIP2024模拟11:忠于自我

    NOIP2024模拟11:忠于自我 T1 一句话题意:有若干个容量为 \(L\) 的包,从左往右装物品,当前包还能装则装,否则必须重开一个包装进去,对于\(\forall i \in [1,n]\), ...

  5. 部署个SSH蜜罐玩玩,又能增强安全性,又能当电子蛐蛐

    前言 最近有个旧服务器的 SSL 证书过期了,每次都申请 SSL 证书太麻烦了,我直接把 swag 方案部署上去. 然后发现这个服务器在安全方面有所疏忽,所以又加固了一下,SSH 部分我想起来之前用过 ...

  6. UE4纯C++实现游戏快捷栏之将快捷栏注册到玩家状态

    我们有了UI有了物品信息,接下来我们便需要给每一个玩家绑定一个快捷栏了,我们分以下几部分来注册我们玩家的快捷栏. 1.Types.h:定于ShortcutContainer类,定义快捷栏的单个容器结构 ...

  7. 【每天学点AI】前向传播、损失函数、反向传播

    在深度学习的领域中,前向传播.反向传播和损失函数是构建和训练神经网络模型的三个核心概念.今天,小编将通过一个简单的实例,解释这三个概念,并展示它们的作用. 前向传播:神经网络的"思考&quo ...

  8. 拯救php性能的神器webman-使用后台webman-admin

    在webman的插件市场里面发现了这个 webman-admin 安装的话很简单,就是在已经安装了 webman 的目录里面执行  composer require -W webman/admin 安 ...

  9. 符合ASTM标准的雨流计数法及其不同的改进方法

    随着研究的深入,人们发现采用时间序列计算载荷谱太麻烦了,处理的工作量太大,我们不需要将每个时刻点的载荷都做运算,疲劳计算只需要提供幅值.均值和循环次数,鉴于此发展出了很多不同的计数方法,雨流法是最常见 ...

  10. BLOG-1

    前言 回顾这三次作业的心路历程,可以说每一次都带来了新的挑战与收获,随着题目数量和复杂度的增加,对Java编程的理解和面向对象设计的认知逐步加深.作为Java编程初学者,最初对编程架构.模块分层和错误 ...