Live2D

Solution -「CF 590E」Birthday

\(\mathscr{Description}\)

  Link.

  给定 \(n\) 个字符串 \(S_{1..n}\),选出其一个最大子集 \(T\),使得 \(T\) 中的字符串两两不存在包含关系。要求构造答案。

  \(n\le750\)\(\sum|S|\le10^7\)

\(\mathscr{Solution}\)

  \(\subseteq\)\(\{S_n\}\) 中是偏序关系,那么最长反链 \(T\) 的大小就是最小链覆盖大小。先建出 AC 自动机,通过 fail 树的相邻祖孙关系(每个点与其最深祖先的关系)建立偏序,再通过 bitset 维护传递闭包。利用经典二分图最大匹配模型就能求出 \(|T|\)

  难点在于 \(T\) 的构造。事实上,这就是普遍的构造最长反链的方法。推荐 C202044zxy 的博客。由于还没有想到 motivated 的讲解,所以直接搬前人的轮子了。(

  提炼一点结论性的过程方便复习:

  • 先求最大匹配。
  • 最大匹配 \(\rightarrow\) 最小点覆盖,二分图上 DFS。
  • 最小点覆盖 \(\rightarrow\) 最大独立集,直接取补集。
  • 实点和虚点都在独立集里的点选入反链。

  实现上,\(\mathcal O(n^3)\) Hungary 然后 DFS 标记一遍就行了,不需要想证明一样分很多步。(所以我还是觉得这个证明写得不自然。

\(\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)

const int MAXN = 750, MAXL = 1e7, IINF = 0x3f3f3f3f;
int n, mtc[MAXN * 2 + 5];
bool vis[MAXN * 2 + 5];
std::string str[MAXN + 5];
std::bitset<MAXN + 5> adj[MAXN + 5];

struct AhoCorasickAutomaton {
    int node, ch[MAXL + 5][2], len[MAXL + 5][2], fail[MAXL + 5], bel[MAXL + 5];
    int que[MAXL + 5], hd, tl;
    AhoCorasickAutomaton(): node(1) {}

    inline int insert(const std::string& str, const int id) {
        int u = 1;
        for (int c: str) {
            if (!ch[u][c -= 'a']) ch[u][c] = ++node;
            u = ch[u][c];
        }
        return bel[u] = id, u;
    }

    inline void build() {
        hd = 1, tl = 0;
        if (ch[1][0]) fail[que[++tl] = ch[1][0]] = 1;
        else ch[1][0] = 1;
        if (ch[1][1]) fail[que[++tl] = ch[1][1]] = 1;
        else ch[1][1] = 1;
        while (hd <= tl) {
            int u = que[hd++];
            if (ch[u][0]) fail[que[++tl] = ch[u][0]] = ch[fail[u]][0];
            else ch[u][0] = ch[fail[u]][0];
            if (ch[u][1]) fail[que[++tl] = ch[u][1]] = ch[fail[u]][1];
            else ch[u][1] = ch[fail[u]][1];
        }
    }
} acam;

struct DSU {
    int fa[MAXL + 5];
    inline int find(const int x) {
        return x == fa[x] ? x : fa[x] = find(fa[x]);
    }
} dsu;

inline bool augment(const int u) {
    rep (v, 1, n) if (adj[u].test(v) && !vis[v]) {
        vis[v] = true;
        if (!mtc[v + n] || augment(mtc[v + n])) {
            mtc[v + n] = u, mtc[u] = v + n;
            return true;
        }
    }
    return false;
}

inline void mark(const int u) {
    vis[u + n] = true;
    rep (v, 1, n) if (adj[v].test(u) && !vis[v]) {
        vis[v] = true, mark(mtc[v] - n);
    }
}

int main() {
    // freopen("a.in", "r", stdin);
    // freopen("a.out", "w", stdout);
    std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);

    std::cin >> n;
    rep (i, 1, n) std::cin >> str[i], acam.insert(str[i], i);
    acam.build();

    rep (i, 1, acam.node) {
        if (i != 1 && !acam.bel[i]) dsu.fa[i] = acam.fail[i];
        else dsu.fa[i] = i;
    }

    rep (i, 1, n) {
        int u = 1;
        for (int c: str[i]) {
            u = acam.ch[u][c - 'a'];
            if (acam.bel[dsu.find(u)]) adj[i].set(acam.bel[dsu.find(u)]);
        }
        u = acam.fail[u]; // When bel[u]=i, there's some thing wrong. Fix it.
        if (acam.bel[dsu.find(u)]) adj[i].set(acam.bel[dsu.find(u)]);
    }
    rep (k, 1, n) rep (i, 1, n) if (adj[i].test(k)) adj[i] |= adj[k];
    rep (i, 1, n) adj[i].reset(i);

    int ans = n;
    rep (i, 1, n) ans -= augment(i), memset(vis, false, sizeof vis);
    rep (i, 1, n) if (!mtc[i + n]) mark(i);

    std::cout << ans << '\n';
    rep (i, 1, n) if (!vis[i] && vis[i + n]) std::cout << i << ' ';
    std::cout << '\n';
    return 0;
}

posted @ 2022-06-12 20:46  Rainybunny  阅读(67)  评论(1编辑  收藏  举报