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

Solution -「CF 590E」Birthday的更多相关文章

  1. Solution -「CF 1342E」Placing Rooks

    \(\mathcal{Description}\)   Link.   在一个 \(n\times n\) 的国际象棋棋盘上摆 \(n\) 个车,求满足: 所有格子都可以被攻击到. 恰好存在 \(k\ ...

  2. Solution -「CF 1622F」Quadratic Set

    \(\mathscr{Description}\)   Link.   求 \(S\subseteq\{1,2,\dots,n\}\),使得 \(\prod_{i\in S}i\) 是完全平方数,并最 ...

  3. Solution -「CF 923F」Public Service

    \(\mathscr{Description}\)   Link.   给定两棵含 \(n\) 个结点的树 \(T_1=(V_1,E_1),T_2=(V_2,E_2)\),求一个双射 \(\varph ...

  4. Solution -「CF 923E」Perpetual Subtraction

    \(\mathcal{Description}\)   Link.   有一个整数 \(x\in[0,n]\),初始时以 \(p_i\) 的概率取值 \(i\).进行 \(m\) 轮变换,每次均匀随机 ...

  5. Solution -「CF 1586F」Defender of Childhood Dreams

    \(\mathcal{Description}\)   Link.   定义有向图 \(G=(V,E)\),\(|V|=n\),\(\lang u,v\rang \in E \Leftrightarr ...

  6. Solution -「CF 1237E」Balanced Binary Search Trees

    \(\mathcal{Description}\)   Link.   定义棵点权为 \(1\sim n\) 的二叉搜索树 \(T\) 是 好树,当且仅当: 除去最深的所有叶子后,\(T\) 是满的: ...

  7. Solution -「CF 623E」Transforming Sequence

    题目 题意简述   link.   有一个 \(n\) 个元素的集合,你需要进行 \(m\) 次操作.每次操作选择集合的一个非空子集,要求该集合不是已选集合的并的子集.求操作的方案数,对 \(10^9 ...

  8. Solution -「CF 1023F」Mobile Phone Network

    \(\mathcal{Description}\)   Link.   有一个 \(n\) 个结点的图,并给定 \(m_1\) 条无向带权黑边,\(m_2\) 条无向无权白边.你需要为每条白边指定边权 ...

  9. Solution -「CF 599E」Sandy and Nuts

    \(\mathcal{Description}\)   Link.   指定一棵大小为 \(n\),以 \(1\) 为根的有根树的 \(m\) 对邻接关系与 \(q\) 组 \(\text{LCA}\ ...

  10. Solution -「CF 487E」Tourists

    \(\mathcal{Description}\)   Link.   维护一个 \(n\) 个点 \(m\) 条边的简单无向连通图,点有点权.\(q\) 次操作: 修改单点点权. 询问两点所有可能路 ...

随机推荐

  1. 22.使用Rancher2.0搭建Kubernetes集群

    使用Rancher2.0搭建Kubernetes集群 中文文档:https://docs.rancher.cn/docs/rancher2 安装Rancher2.0 使用下面命令,我们快速的安装 # ...

  2. OpenCV开发笔记(八十二):两图拼接使用渐进色蒙版场景过渡缝隙

    前言   对于图像拼接,前面探讨了通过基于Stitcher进行拼接过渡和基于特征点进行拼接过渡,这2个过渡的方式是摄像头拍摄角度和方向不应差距太大.  对于特定的场景,本身摄像头拍摄角度差距较大,拉伸 ...

  3. Air780E之TCP应用,你了解吗?

    ​ 一.TCP简介 TCP(TransmissionControlProtocol,传输控制协议)是一种面向连接的.可靠的.基于字节流的传输层通信协议.它主要用于在不可靠的网络环境中提供稳定的数据传输 ...

  4. NZOJ 模拟赛4

    T1 数字游戏 大家列队后,都觉得累了,于是一起坐到院子中的草地上休息.这时Anna突然想跟她的最大竞争对手Cici玩一个数字游戏,她要你编写程序帮助她取得胜利. 第i次游戏初始时有一个整数N_i(1 ...

  5. php使用汉字作为进制转换

    突然想到英文字符26个,大小写共52个,数字10个,加起来也不过62,再算上特殊字符,也就90个,可以看这篇文章 那可不可以扩大这个进制呢?我想到了汉字. 中文汉字,博大精深,我们就用常用汉字2500 ...

  6. nginx防盗链接的使用

    以 local.hyperf.com为例 nginx配置文件如下 # 至少需要一个 Hyperf 节点,多个配置多行 upstream hyperf { # Hyperf HTTP Server 的 ...

  7. ubuntu界面文件夹出现了很多隐藏文件夹如何不显示

    突然发现文件夹下很多隐藏文件夹都显示出来了,也不知道怎么弄得,想隐藏他们,百度提问无果,提到隐藏这种关键字全是ls这种让我终端查看文件列表. 自己仔细看了看文件夹资源管理器,发现有个按钮 就是这个选项 ...

  8. XSS跨站脚本之portswigger labs练习

    目录 1 什么是XSS 2 XSS的类型有哪些 3 XSS攻击的过程和原理 4 XSS的防御 5 可能会用到的XSS Payload资源 6 靶场训练 portswigger labs 6.1 没有任 ...

  9. Spring AOP实现原理与CGLIB应用

    AOP(Aspect Orient Programming),也就是面向方面编程,作为面向对象编程的一种补充,专门用于处理系统中分布于各个模块(不同方法)中的交叉关注点的问题,在 Java EE 应用 ...

  10. elementUI 选择开始结束日期加限制

    需求是开始结束日期不得大于当前时间,当开始日期发生变化时,结束日期不得小于开始日期且不得大于当前日期 <el-form-item label="开始日期:"> < ...