Live2D

Solution -「CF 590E」Birthday

Description

  Link.

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

  n750|S|107

Solution

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

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

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

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

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

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 @   Rainybunny  阅读(68)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
历史上的今天:
2020-06-12 Solution -「NOI.AC 省选膜你赛」T2
点击右上角即可分享
微信分享提示