题解:CF590E Birthday
Tag:二分图匹配,AC 自动机,Dilworth,Konig。
题意:给定
范围:
解法:先考虑求出任意两个字符串之间的包含关系。这一部分可以通过建立 AC 自动机求出。
具体来说,建立 AC 自动机,其上的字符串结尾位置只有
事实上容易发现这个包含关系是偏序关系,我们要求最长反链,根据 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;
}
分类:
题解
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 张高兴的大模型开发实战:(一)使用 Selenium 进行网页爬虫
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构