Live2D

Note -「拟阵交」& Solution -「CF 1284G」Seollal

Description

  Link.

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

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

  n,m20,其实有多测,但根据数据规则可忽略。

Solution

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

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

M1=(U,I1), I1={SUno circle in G=(V,S)};M2=(U,I2), I2={SUuVblack,du2}.

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


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

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

  形式化地,对于定义在 U 上的 k 个拟阵 M1..k,它们的交为

N=(U,I1I2).

为什么记作 N?因为这一交的结果很可能不是拟阵。而对 N 的集族中最大集合的求解,就是所谓 k拟阵交问题,不妨记作 kMIkMI 可以描述为:输入 M1..k 与整数 n,判断是否存在 |S|n,SN

  当 k3 时,喜闻乐见,kMI 是 NPC 问题。

证明

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

  对于一个二分连通图 G=(V=VLVR,E),构造三个 E 上的集族

IG={SEno circle in G=(V,S)},IL={SEuVGL,du2},IR={SEuVGR,du2}.

MG=(E,IG) 即图拟阵,亦不难证明 ML=(E,IL)MR=(E,IR) 为拟阵。令 N=(E,IGILIR),我们断言,G 存在 Hamilton 回路,当且仅当 SN,|S|=|V|1

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

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

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

 

  不过,当 k=2 时,拟阵交是 P 的,下面来构建这个算法。算法的核心是,对于当前解 I,构造一个有向二分图——交换图 GM1,M2(I)=(V=VLVR,E),其中

VL=I, VR=UI;E={x,yVL×VR(I{x}){y}I1} {x,yVR×VL(I{x}){y}I2}.

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

Algorithm: 2MI.Input: two matroid M1=(U,I1) and M2=(U,I2).Output: an intersection I of M1 and M2 of max size.1I2repeat:3GGM1,M2(I)4X1{eUII{e}I1}5X2{eUII{e}I2}6Pthe shortest path from X1 to X2 in G7II Δ P8until P does not exist.9return I.

其中 I Δ P 为集合对称差,可以理解作集合异或。

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

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

证明

  给出一个定理:

  最大最小定理 对于上述的 M1,M2,有

maxII1I2{|I|}=minSU{r1(S)+r2(US)}.

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

  首先,LHSRHS 是显然的,我们只需要证明不等式的取等。令 I 为上述算法的输出,SGM1,M2(I) 上所有可达 X2 的点集。尝试证明:r1(S)|IS|

  反证。若 r1(S)>|IS|,则 xSI,使得 (IS){x}I1。另一方面,由于 X1,X2 不连通,所以 I{x}I1。注意到 II1,所以 I{x} 含有恰一个环 C。如果 yC{x}, yS,那么 C(IS){x},这与其 I1 矛盾。因此 yC, yS。此时,(I{y}){x}I1。进一步地 y,xEG,则 yS,矛盾。

  r2(US)|I(US)| 同理。又由于

|I|=|IS|+|I(US)|r1(S)+r2(US),

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

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

  写的时候注意分清 I1,I2,一个集合到底需要在谁中的独立。调麻了 qwq。

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;
}

posted @   Rainybunny  阅读(111)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示