• 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_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}{} 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;
    }
    
    
  • 相关阅读:
    python的性能了解
    工作记录01/17/11
    继承或者重写django的user model?
    dunder=double underscore
    ipython应该是个好的命令行式的python ide,不过现在没时间折腾。
    django的settings如何在不同环境下进行切换
    pythonic实践
    关于递归函数的简单认识
    数据结构(C语言版)链表相关操作算法的代码实现
    数据结构(C语言版)顺序栈相关算法的代码实现
  • 原文地址:https://www.cnblogs.com/rainybunny/p/16353591.html
Copyright © 2020-2023  润新知