题解:CF590E Birthday
Tag:二分图匹配,AC 自动机,Dilworth,Konig。
题意:给定 个互不相同字符串 。求最多能保留多少个字符串,使得两两之间互不包含。
范围:,,字符串只包含 。
解法:先考虑求出任意两个字符串之间的包含关系。这一部分可以通过建立 AC 自动机求出。
具体来说,建立 AC 自动机,其上的字符串结尾位置只有 个。我们称这些点为关键点。考虑对于 Fail 树上每个点,求出第一个非自身的祖先关键点,然后就可以将树简化,从而使 Fail 树变成只有 个节点,然后就可以用一般的暴力跳 Fail 维护即可。
事实上容易发现这个包含关系是偏序关系,我们要求最长反链,根据 Dilworth 定理变成最小路径覆盖,然后变成了 DAG 上的最小路径覆盖,直接拆点变成二分图匹配问题。答案为 减最大匹配。
至于构造答案,根据 Konig 定理可以将最大匹配变成最小点覆盖,然后参考 Dilworth 的证明部分即知,对于最长反链 ,, 和 分别是拆点出来的两部分, 是最小点覆盖集。
复杂度 。
代码:
#pragma GCC optimize("-Ofast,fast-math,-inline")
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <bitset>
#include <string>
#include <queue>
#include <vector>
using namespace std;
const int T = 755, N = 1505, M = 1e7 + 5;
int n;
string s[N];
bool g[T][T];
class ACAM
{
public:
int son[M][2];
// fa->fail
int pos[M]; // 每个点往上第一个标记点祖先
int fail[M], idx;
int flag[M];
bitset<M> rt;
vector<int> G[N];
void ins(string& s, int j)
{
int u = 0;
for (auto& i : s)
{
int j = i - 'a';
if (!son[u][j]) son[u][j] = ++idx;
u = son[u][j];
}
flag[u] = j;
}
void build()
{
queue<int> q;
if (son[0][0]) q.push(son[0][0]);
if (son[0][1]) q.push(son[0][1]);
while (q.size())
{
int u = q.front();
q.pop();
for (auto& i : { 0, 1 })
{
if (son[u][i])
{
fail[son[u][i]] = son[fail[u]][i];
q.push(son[u][i]);
}
else son[u][i] = son[fail[u]][i];
}
}
}
void solve()
{
memset(pos, -1, sizeof pos);
for (int i = 1; i <= idx; i++)
{
int fa = fail[i];
if (!fa) pos[i] = 0;
else
{
if (flag[fa])
{
pos[i] = fa;
continue;
}
int j = fa;
vector<int> vec;
while (!flag[j] && j)
{
vec.emplace_back(j);
if (~pos[j]) j = pos[j];
else j = fail[j];
}
pos[i] = j;
for (auto& k : vec) pos[k] = j;
}
}
for (int i = 1; i <= idx; i++)
{
if (flag[i])
{
// fa=pos[i]
int fa = pos[i];
if (fa)
{
G[flag[fa]].emplace_back(flag[i]);
rt[flag[i]] = 1;
}
}
}
}
void match(string& s, int id)
{
int u = 0;
for (auto& c : s)
{
u = son[u][c - 'a'];
if (flag[u]) g[flag[u]][id] = 1;
int f = pos[u];
if (f)
{
g[flag[f]][id] = 1;
}
}
}
void dfs(int u)
{
for (auto& j : G[u])
{
dfs(j);
for (int k = 1; k <= n; k++) if (g[j][k]) g[u][k] = 1;
}
}
void build_graph()
{
for (int i = 1; i <= n; i++)
{
if (!rt[i])
{
dfs(i);
}
}
}
}acam;
int match[N], pre[N];
bitset<N> vis;
bitset<N> vertexcover;
inline bool find_path()
{
for (int i = 1; i <= 2 * n; i++) pre[i] = vis[i] = 0;
queue<int> q;
for (int i = 1; i <= n; i++) if (!match[i]) q.push(i);
while (q.size())
{
int u = q.front();
q.pop();
if (vis[u]) continue;
vis[u] = 1;
for (int j = n + 1; j <= 2 * n; j++)
{
if (j == match[u] || pre[j] || !g[u][j - n]) continue;
pre[j] = u;
int k = match[j];
vis[j] = 1;
if (!k) // 增广路
{
int x = j;
while (true)
{
int y = pre[x], z = match[y];
match[y] = x, match[x] = y;
if (!z) return 1;
x = z;
}
}
else
{
if (!vis[k]) q.push(k);
}
}
}
return 0;
}
inline void solve()
{
find_path();
for (int i = n + 1; i <= 2 * n; i++) if (vis[i]) vertexcover[i] = 1;
for (int i = 1; i <= n; i++) if (!vis[i]) vertexcover[i] = 1;
}
int main() // 1
{
ios::sync_with_stdio(0), cin.tie(0);
cin >> n;
for (int i = 1; i <= n; i++) cin >> s[i];
for (int i = 1; i <= n; i++) acam.ins(s[i], i);
acam.build();
acam.solve();
for (int i = 1; i <= n; i++)
{
acam.match(s[i], i);
}
acam.build_graph();
for (int i = 1; i <= n; i++)
{
g[i][i] = 0;
}
int ans = 0;
while (find_path()) ans++;
cout << n - ans << "\n";
solve();
for (int i = 1; i <= n; i++)
{
if (!vertexcover[i] && !vertexcover[i + n]) cout << i << " ";
}
cout << "\n";
return 0;
}