Note -「拟阵交」& Solution -「CF 1284G」Seollal
\(\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_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\)?因为这一交的结果很可能不是拟阵。而对 \(\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_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\)。
注:我对 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)\),其中
\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}
\]
算法的其他部分比较平凡。给出算法:
\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\),有
\]
我们指出,该算法所输出的 \(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~\Delta~P\) 恒为独立集。这里先偷个懒,去读论文嘛。不过值得一提的是,\(P\) 是最短路保证了 \(P\) 除起点外的 \(|P|-1\) 个点在二分图上有唯一完美匹配,因而才能保证 \(I~\Delta~P\) 独立。
写的时候注意分清 \(\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的更多相关文章
- Solution -「CF 1342E」Placing Rooks
\(\mathcal{Description}\) Link. 在一个 \(n\times n\) 的国际象棋棋盘上摆 \(n\) 个车,求满足: 所有格子都可以被攻击到. 恰好存在 \(k\ ...
- Solution -「CTS 2019」「洛谷 P5404」氪金手游
\(\mathcal{Description}\) Link. 有 \(n\) 张卡牌,第 \(i\) 张的权值 \(w_i\in\{1,2,3\}\),且取值为 \(k\) 的概率正比于 \ ...
- Solution -「BZOJ 3812」主旋律
\(\mathcal{Description}\) Link. 给定含 \(n\) 个点 \(m\) 条边的简单有向图 \(G=(V,E)\),求 \(H=(V,E'\subseteq E)\ ...
- Solution -「GLR-R2」教材运送
\(\mathcal{Description}\) Link. 给定一棵包含 \(n\) 个点,有点权和边权的树.设当前位置 \(s\)(初始时 \(s=1\)),每次在 \(n\) 个结点内 ...
- Android内存管理(4)*官方教程 含「高效内存的16条策略」 Managing Your App's Memory
Managing Your App's Memory In this document How Android Manages Memory Sharing Memory Allocating and ...
- Git 执行 「fork 出来的仓库」和「最新版本的原仓库」内容同步更新
当我们在 GitHub 上 fork 出一个仓库后,如果原仓库更新了,此时怎样才能保证我们 fork 出来的仓库和原仓库内容一致呢?我们一般关注的是仓库的 master(主干分支)的内容,通过以下步骤 ...
- FileUpload控件「批次上传 / 多档案同时上传」的范例--以「流水号」产生「变量名称」
原文出處 http://www.dotblogs.com.tw/mis2000lab/archive/2013/08/19/multiple_fileupload_asp_net_20130819. ...
- SSH连接时出现「WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!」解决办法
用ssh來操控github,沒想到連線時,出現「WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!」,後面還有一大串英文,這時當然要向Google大神求助 ...
- Java的参数传递是「值传递」还是「引用传递」?
关于Java传参时是引用传递还是值传递,一直是一个讨论比较多的话题. 有人说Java中只有值传递,也有人说值传递和引用传递都是存在的,比较容易让人产生疑问. 关于值传递和引用传递其实需要分情况看待. ...
- 「ZJOI2019」&「十二省联考 2019」题解索引
「ZJOI2019」&「十二省联考 2019」题解索引 「ZJOI2019」 「ZJOI2019」线段树 「ZJOI2019」Minimax 搜索 「十二省联考 2019」 「十二省联考 20 ...
随机推荐
- PostgreSQL中将对象oid和对象名相互转换
PostgreSQL中将对象oid转为对象名 使用pg的内部数据类型将对象oid转为对象名,可以简化一些系统视图的关联查询. 数据库类型转换对应类型的oid 可以用以下数据库类型转换对应类型的oid( ...
- 干货分享:Air780E软件指南:字符串处理
一.Lua字符串介绍 关于字符串,Lua提供了一些灵活且强大的功能,一些入门知识如下: 1.1 字符串定义 在Lua中,字符串可以用单引号'或双引号"来定义.例如: localstr1='H ...
- Air780E软件指南:C语言内存数组(zbuff)
一.ZBUFF(C内存数组)简介 zbuff库可以用c风格直接操作(下标从0开始),例如buff[0]=buff[3] 可以在sram上或者psram上申请空间,也可以自动申请(如存在psram则在p ...
- 2024-11-16:哈沙德数。用go语言,如果一个整数能够被它的各个数位上数字的和整除, 我们称这个整数为哈沙德数(Harshad number)。 给定一个整数 x, 如果 x 是哈沙德数,则返回
2024-11-16:哈沙德数.用go语言,如果一个整数能够被它的各个数位上数字的和整除, 我们称这个整数为哈沙德数(Harshad number). 给定一个整数 x, 如果 x 是哈沙德数,则返回 ...
- orange pi 香橙派 zero 刷openwrt当作有wifi的小路由器用
前面写过我用香橙派zero来测量温度 https://www.cnblogs.com/jar/p/15848178.html 最近准备把他改造成路由器 https://www.right.com.cn ...
- CF2027D The Endspeaker (Hard Version) 题解
题面 给你一个长度为 \(n\) 的数组 \(a\) 和一个长度为 \(m\) 的数组 \(b\) (所有 \(1 \le i < m\) 满足 \(b_i > b_{i+1}\) ).最 ...
- 【一步步开发AI运动小程序】十七、如何识别用户上传视频中的人体、运动、动作、姿态?
[云智AI运动识别小程序插件],可以为您的小程序,赋于人体检测识别.运动检测识别.姿态识别检测AI能力.本地原生识别引擎,内置10余个运动,无需依赖任何后台或第三方服务,有着识别速度快.体验佳.扩展性 ...
- json-lib(ezmorph)、gson、flexJson、fastjson、jackson对比,实现java转json,json转java
json-lib(ezmorph).gson.flexJson.fastjson.jackson对比,实现java转json,json转java 本文中所讲的所有代码都在此:json-test 目前关 ...
- 优秀的 Java 程序员所应该知道的 Java 知识
JDK 相关知识 JDK 的使用 JDK 源代码 JDK 相应技术背后的原理 JVM 相关知识 服务器端开发需要重点熟悉的 Java 技术 Java 并发 Java IO 开源框架 Java 之外的知 ...
- Python之时间日期操作
常用时间操作的函数汇总, 涵盖 常用的time datetime 1.计算两个日期相差天数 import datetime str1 = '2021-10-20' str2 = '2021-10- ...