Loading

【学习笔记】从 CTSC 2008 祭祀一题谈起的最长反链相关问题的解决方法

本题的题意:给出一个 DAG,求最长反链。

Dilworth 定理:偏序集上最长反链的长度等于最小链覆盖中链的数量。

先做一遍传递闭包,使得 DAG 中 \(x<y\) 的定义"从 \(x\)\(y\) 有连边"变为了"从 \(x\) 出发能够到达 \(y\)“,此时就可以直接套用 Dilworth 定理了。

1. 求解最小链覆盖大小

将所有的点拆为 出(\(x_0\))、入(\(x_1\)) 两点,形成一个二分图。对于 DAG 上的 \(x<y\),连边 \((x_0,y_1)\) 。因为每个出点只能匹配一个入点,每个入点只能匹配一个出点,此时求该二分图的最大匹配

令最大匹配大小为 \(m\),假设最开始每个点自成一条链,接下来每匹配一条边那么最小链覆盖大小就减一,所以最小链覆盖大小为 \(n-m\) 。当然从最大匹配可以还原出最小链覆盖。

2. 构造最长反链方案

这个部分较复杂。

  • Step 1. 求出最小顶点覆盖

    结论:最小顶点覆盖大小等于最大匹配大小

    考虑这么一个构造最小顶点覆盖的方法:选取右侧没有匹配点开始 dfs,从右侧往左侧 dfs 走非匹配边,左侧往右侧走匹配边

    那么最小顶点覆盖的方案为:左侧被访问过的点,右侧没被访问过的点

    接下来考虑证明两个问题:1. 最小顶点覆盖大小 等于 最大匹配大小。 2. 这样选出来的点集的确覆盖了所有的边。

    先考虑第一个问题:可以发现右侧留下的点一定都是匹配边端点,并且其匹配的左侧点一定没有被访问过(否则右侧的这个点也会被访问),不会留下。同时可以发现左侧留下的点一定都是匹配边端点(否则出现一组新的匹配),并且其匹配的右侧点一定被访问过。

    也就是说,留下的肯定都是匹配边端点,并且每条匹配边恰好留下一个端点,所以有 最小顶点覆盖大小 等于 最大匹配大小

    接着考虑第二个问题:讨论每一条边,如果这条边的左侧端点被访问过,或者右侧端点没被访问过,那么这条边必定被覆盖。接下来只需要讨论"左侧端点没被访问过,右侧端点被访问过"这种边了,可以证明这种边不存在:

    • 假设右侧端点是非匹配点,那么没访问左侧端点的情况只有该边是匹配边,与假设矛盾。
    • 假设右侧端点是匹配点,那么一定是从左侧的另一条匹配边访问过来的(如果从当前边访问过来显然矛盾),也就是说,当前边一定是非匹配边,所以左侧端点必然被访问,与条件矛盾。

    所以所有遍都被覆盖了,我们选出来了最小顶点覆盖。

  • Step 3. 求出最大独立集

    结论:最大独立集、最小顶点覆盖互为补集

    考虑最小顶点覆盖的补集中的一个点,与其相邻的点必须都在最小顶点覆盖中,才能够覆盖所有相邻的边。这句话的意思是,最小顶点覆盖的补集中的点两两不相邻。

    同时考虑最大独立集的补集中的一个点,与该点相邻的点中一定有最大独立集中的点,为了覆盖这之间的边,该点必须属于最小定点覆盖。

  • Step 2. 通过最大独立集构造最长反链方案

    考虑这么一个做法:对于每个 \(x\),如果 \(x_0,x_1\) 都在最大独立集中,那么将 \(x\) 加入到反链中。

    注意到因为最小顶点覆盖大小为 \(m\),所以最大独立集大小为 \(2n-m\) 。同时令构造出的反链大小为 \(t\),那么最大独立集(记为 \(S\))大小为 \(t+\sum\limits_{x}[(x_0\in S)\lor (x_1\in S)]\),注意到因为后面部分不超过 \(n\),所以可以得到 \(t\geq n-m\)

    也就是说 \(t\) 不小于 \(n-m\) 。事实上,根据 Dilworth 定理,\(t\) 恰好为 \(n-m\),我们构造出了最长反链方案。

3. 完成本题的第三问

本篇文章的主体内容到这里就结束了,接下来是"祭祀"一题第三问的做法。

其实只需要删除该点以及相邻的点,然后再求最大反链即可,如果大小差为 \(1\) 说明可以。时间复杂度为 \(O(n^2m)\)

4. 代码实现

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef pair <int, int> pii;

#define fi first
#define se second
#define rez resize
#define pb push_back
#define mkp make_pair

#define Lep(i, l, r) for (int i = l; i < r; ++ i)
#define Rep(i, r, l) for (int i = r; i > l; -- i)
#define lep(i, l, r) for (int i = l; i <= r; ++ i)
#define rep(i, r, l) for (int i = r; i >= l; -- i)

const int mod = 1e9 + 7;

inline int mul (int x, int y) { return 1ll * x * y % mod; }
inline void sub (int &x, int y) { x -= y; if (x < 0) x += mod; }
inline void pls (int &x, int y) { x += y; if (x >= mod) x -= mod; }
inline int dec (int x, int y) { x -= y; if (x < 0) x += mod; return x; }
inline int add (int x, int y) { x += y; if (x >= mod) x -= mod; return x; }
inline int modpow (int x, ll y, int res = 1) {
    for (y = (y + mod - 1) % (mod - 1); y; y >>= 1, x = mul (x, x)) if (y & 1) res = mul (x, res);
    return res;
}

char _c; bool _f; template <class T> inline void IN (T & x) {
    x = 0, _f = 0; while (_c = getchar (), ! isdigit (_c)) if (_c == '-') _f = 1;
    while (isdigit (_c)) x = x * 10 + _c - '0', _c = getchar (); if (_f) x = -x;
}

template <class T> inline void chkmin (T & x, T y) { if (x > y) x = y; }
template <class T> inline void chkmax (T & x, T y) { if (x < y) x = y; }

const int N = 1e2 + 5;

bool tag[N << 1], vis[N << 1], able[N], G[N][N];
int n, m, match[N << 1];

bool solve (int u) {
    if (vis[u]) return false;

    vis[u] = true;
    lep (i, 1, n) if (G[u][i] && able[i])
        if (! match[i + n] || solve (match[i + n]))
            return match[i + n] = u, match[u] = i + n, true;
    return false;
}
void addtag (int u) {
    tag[u] = vis[u] = true;
    lep (i, 1, n) if (G[i][u - n] && (! vis[i] && u != match[i])) {
        tag[i] = vis[i] = true;
        if (match[i]) addtag (match[i]);
    }
}

int main () {
    IN (n), IN (m);
    for (int i = 1, u, v; i <= m; ++ i) IN (u), IN (v), G[u][v] = true;
    lep (k, 1, n) lep (i, 1, n) lep (j, 1, n) G[i][j] |= G[i][k] & G[k][j];

    int ans = 0;
    lep (i, 1, n) able[i] = true;
    lep (i, 1, n) memset (vis, 0, sizeof vis), ans += solve (i);
    printf ("%d\n", n - ans);

    memset (vis, 0, sizeof vis);
    lep (i, 1, n) if (! match[i + n] && ! vis[i + n]) addtag (i + n);
    lep (i, 1, n) putchar ((! tag[i] && tag[i + n]) ? '1' : '0');
    puts ("");

    lep (i, 1, n) {
        int nown = 0, nans = 0;
        lep (j, 1, n) able[j] = ! (i == j || (G[i][j] || G[j][i])), nown += able[j];

        memset (match, 0, sizeof match);
        lep (j, 1, n) if (able[j]) memset (vis, 0, sizeof vis), nans += solve (j);
        putchar ((nown - nans + 1 == n - ans) ? '1' : '0');
    }
    puts ("");
    return 0;
}
posted @ 2021-09-03 20:29  Qiuly  阅读(404)  评论(0编辑  收藏  举报