Codeforces 590E - Birthday(AC 自动机+Dilworth 定理+二分图匹配)
AC 自动机有时只是辅助建图的工具,真的
首先看到多串问题,果断建出 AC 自动机。设 \(m=\sum|s_i|\)。
不难发现子串的包含关系构成了一个偏序集,于是我们考虑转化为图论,若 \(s_j\) 包含于 \(s_i\) 则连一条 \(i\to j\) 的边。显然利用 AC 自动机可实现 \(\mathcal O(m)\) 建图。
题目要我们求的实际上是该偏序集的最大反链大小,根据 Dilworth 定理可将其转化为最小可相交覆盖的大小。
而最小可相交链覆盖的大小又可以通过传递闭包转化为最小不可相交链覆盖的问题,最小不可相交问题又可通过拆点二分图求出。故第一问答案就是 \(n-\) 拆点二分图最大匹配,这个想怎么搞怎么搞,网络流、匈牙利皆可(然鹅 wtcl 不会匈牙利只好跑网络流了)。
至于输出方案……这个嘛,考虑我们当时求最小边覆盖是如何构造方案的,就一遍 DFS 求出源点能到达的点,那么最小边覆盖就是二分图左部不能到达的点 \(+\) 二分图右部能到达的点。最大独立集就求个补集就行了。
值得注意的一点是此题 \(m\) 高达 \(10^7\),递归显然会爆栈,故不能通过建出 fail 树并在 fail 树上一遍 DFS 实现建图。考虑在求 fail 数组的时候再记录一个 \(pos_i\) 表示 \(i\) 在 fail 树的祖先中离它最近的是某个串结尾位置的节点,建图的时候就枚举字符串 \(s_i\) 并遍历根到 \(s_i\) 结尾位置的路径上所有点,若发现某个点的 \(pos\) 值非零就连一条 \(i\to pos_x\) 的边,如果 \(fail_i\) 的 \(pos\) 值非零那也连一条 \(i\to pos_{fail_i}\) 的边,再 \(n^3\) 求遍传递闭包即可建出图来,正确性显然,并且巧妙地避开了递归爆栈的问题。
代码(荣 膺 最 劣 解):
#include <bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define fz(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
#define ffe(it,v) for(__typeof(v.begin()) it=v.begin();it!=v.end();it++)
#define fill0(a) memset(a,0,sizeof(a))
#define fill1(a) memset(a,-1,sizeof(a))
#define fillbig(a) memset(a,63,sizeof(a))
#define pb push_back
#define ppb pop_back
#define mp make_pair
template<typename T1,typename T2> void chkmin(T1 &x,T2 y){if(x>y) x=y;}
template<typename T1,typename T2> void chkmax(T1 &x,T2 y){if(x<y) x=y;}
typedef pair<int,int> pii;
typedef long long ll;
template<typename T> void read(T &x){
x=0;char c=getchar();T neg=1;
while(!isdigit(c)){if(c=='-') neg=-1;c=getchar();}
while(isdigit(c)) x=x*10+c-'0',c=getchar();
x*=neg;
}
const int MAXN=750;
const int MAXLEN=1e7;
const int MAXV=1502;
const int MAXE=1.5e6;
const int INF=0x3f3f3f3f;
int n;string s[MAXN+5];
int ch[MAXLEN+5][2],fail[MAXLEN+5],pos[MAXLEN+5],ncnt=0;
bool d[MAXN+5][MAXN+5];
void insert(string s,int id){
int cur=0;
for(int i=0;i<s.size();i++){
if(!ch[cur][s[i]-'a']) ch[cur][s[i]-'a']=++ncnt;
cur=ch[cur][s[i]-'a'];
} pos[cur]=id;
}
void getfail(){
queue<int> q;
for(int i=0;i<2;i++) if(ch[0][i]) q.push(ch[0][i]);
while(!q.empty()){
int x=q.front();q.pop();
for(int i=0;i<2;i++){
if(ch[x][i]){
fail[ch[x][i]]=ch[fail[x]][i];q.push(ch[x][i]);
if(!pos[ch[x][i]]) pos[ch[x][i]]=pos[fail[ch[x][i]]];
} else ch[x][i]=ch[fail[x]][i];
}
}
}
int S=1501,T=1502;
int hd[MAXV+5],to[MAXE+5],cap[MAXE+5],nxt[MAXE+5],ec=1;
void adde(int u,int v,int f){
to[++ec]=v;cap[ec]=f;nxt[ec]=hd[u];hd[u]=ec;
to[++ec]=u;cap[ec]=0;nxt[ec]=hd[v];hd[v]=ec;
}
int dep[MAXV+5],now[MAXV+5];
bool getdep(){
memset(dep,-1,sizeof(dep));dep[S]=0;
queue<int> q;q.push(S);now[S]=hd[S];
while(!q.empty()){
int x=q.front();q.pop();
for(int e=hd[x];e;e=nxt[e]){
int y=to[e],z=cap[e];
if(!~dep[y]&&z){dep[y]=dep[x]+1;now[y]=hd[y];q.push(y);}
}
} return ~dep[T];
}
int getflow(int x,int f){
if(x==T) return f;int ret=0;
for(int &e=now[x];e;e=nxt[e]){
int y=to[e],z=cap[e];
if(z&&dep[y]==dep[x]+1){
int w=getflow(y,min(f-ret,z));
ret+=w;cap[e]-=w;cap[e^1]+=w;
if(f==ret) return ret;
}
} return ret;
}
int dinic(){
int ret=0;
while(getdep()) ret+=getflow(S,INF);
return ret;
}
bool vis[MAXV+5];
void dfs(int x){
if(vis[x]) return;vis[x]=1;
for(int e=hd[x];e;e=nxt[e]){
int y=to[e],z=cap[e];
if(z) dfs(y);
}
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++) cin>>s[i],insert(s[i],i);
getfail();
// for(int i=1;i<=ncnt;i++) printf("%d\n",pos[i]);
for(int i=1;i<=n;i++){
int cur=0;
for(int j=0;j<s[i].size();j++){
if(pos[cur]) d[i][pos[cur]]=1;
cur=ch[cur][s[i][j]-'a'];
} if(pos[fail[cur]]) d[i][pos[fail[cur]]]=1;
}
for(int k=1;k<=n;k++) for(int i=1;i<=n;i++) for(int j=1;j<=n;j++)
d[i][j]|=d[i][k]&d[k][j];
for(int i=1;i<=n;i++) for(int j=1;j<=n;j++){
if(d[i][j]&&i!=j) adde(i,j+n,1);
}
for(int i=1;i<=n;i++) adde(S,i,1),adde(i+n,T,1);
printf("%d\n",n-dinic());dfs(S);vector<int> ans;
for(int i=1;i<=n;i++) if(vis[i]&&!vis[i+n]) ans.pb(i);
sort(ans.begin(),ans.end());ffe(it,ans) printf("%d ",*it);
return 0;
}
Codeforces 590E - Birthday(AC 自动机+Dilworth 定理+二分图匹配)的更多相关文章
- [BZOJ1143][CTSC2008]祭祀river(Dilworth定理+二分图匹配)
题意:给你一张n个点的DAG,最大化选择的点数,是点之间两两不可达. 要从Dilworth定理说起. Dilworth定理是定义在偏序集上的,也可以从图论的角度解释.偏序集中两个元素能比较大小,则在图 ...
- Codeforces 163E(ac自动机、树状数组)
要点 显然ac自动机的板子就可以暴力一下答案了 为了优化时间复杂度,考虑套路fail树的dfs序.发现本题需要当前这个尾点加上所有祖先点的个数,考虑使用树状数组差分一下,在父点+1,在子树后-1,每次 ...
- AC自动机——多个kmp匹配
(并不能自动AC) 介绍: Aho-Corasick automaton,最经典的处理多个模式串的匹配问题. 是kmp和字典树的结合. 精髓与灵魂: ①利用trie处理多个模式串 ②引入fail指针. ...
- ac自动机暴力跳fail匹配——hdu5880
很简单的题,ac自动机里再维护一个len表示每个状态的串长,用s去query时每到一个结点都要暴力跳fail,因为有可能这个结点不是,但是其fail是危险结点,找到一个就直接break 再用个差分数组 ...
- Codeforces 739D - Recover a functional graph(二分图匹配)
Codeforces 题面传送门 & 洛谷题面传送门 首先假设我们已经填好了所有问号处的值怎样判断是否存在一个合法的构造方案,显然对于一种方案能够构造出合法的基环内向森林当且仅当: \(\fo ...
- Codeforces 547E - Mike and Friends(AC 自动机+树状数组)
题面传送门 好久每做过 AC 自动机的题了--做几个题回忆一下罢 AC 自动机能够解决多串匹配问题,注意是匹配,碰到前后缀的问题那多半不在 AC 自动机能解决的范围内. 在初学 AC 自动机的时候相信 ...
- AC 自动机
AC自动机(Aho-Corasick Automata)是经典的多模式匹配算法.从前我学过这个算法,但理解的不深刻,现在已经十分不明了了.现在发觉自己对大部分算法的掌握都有问题,决定重写一系列博客把学 ...
- HDU-4518 吉哥系列故事——最终数 AC自动机+数位DP
题意:如果一个数中的某一段是长度大于2的菲波那契数,那么这个数就被定义为F数,前几个F数是13,21,34,55......将这些数字进行编号,a1 = 13, a2 = 21.现给定一个数n,输出和 ...
- UVa 11468 (AC自动机 概率DP) Substring
将K个模板串构成一个AC自动机,那些能匹配到的单词节点都称之为禁止节点. 然后问题就变成了在Tire树上走L步且不经过禁止节点的概率. 根据全概率公式用记忆化搜索求解. #include <cs ...
随机推荐
- 解决el-checkbox-group 的v-model无法绑定对象数组
elementUI官方文档中el-checkbox-group组件绑定的都为一维数组,真实业务中数据绑定往往是多个键值对的对象数组,本文主要解决这个问题. 如下代码: <el-checkbox- ...
- WPF中的命令(Command)
这节来讲一下WPF中的命令(Command)的使用. [认识Command] 我们之前说过,WPF本身就为我们提供了一个基础的MVVM框架,本节要讲的命令就是其中一环,通过在ViewModel中声明命 ...
- 【UE4 设计模式】外观模式 Facade Pattern
概述 描述 外部与一个子系统的通信必须通过一个统一的外观对象进行,为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用.外观模式又称为门面模式,它是一 ...
- Vite启动后提示Network: use `--host` to expose
当使用 Vite 构建项目后,发现只有localhost + 端口 服务,没有 IP + 端口服务. 运行npm run dev,终端提示Vite启动后提示Network: use '--host' ...
- 吴恩达深度学习课后习题第5课第1周第3小节: Jazz Improvisation with LSTM
目录 Improvise a Jazz Solo with an LSTM Network Packages 1 - Problem Statement 1.1 - Dataset What are ...
- 实用小工具:screen
实用小工具:screen 首先,吹爆screen screen,实现了不间断的会话服务,通过SSH连接至远程服务器,当使用了screen开启的会话,不会因为你断开SSH而中断在远程服务器上运行的命令. ...
- 一文读懂Android进程及TCP动态心跳保活
一直以来,APP进程保活都是 各软件提供商 和 个人开发者 头疼的问题.毕竟一切的商业模式都建立在用户对APP的使用上,因此保证APP进程的唤醒,提升用户的使用时间,便是软件提供商和个人开发者的永恒追 ...
- (总结)Linux下su与su -命令的本质(转)
转载地址:http://www.ha97.com/4001.html 本人以前一直习惯直接使用root,很少使用su,前几天才发现su与su -命令是有着本质区别的! 大部分Linux发行版的默认账户 ...
- 二进制小数 牛客网 程序员面试金典 C++ Python
二进制小数 牛客网 程序员面试金典 题目描述 有一个介于0和1之间的实数,类型为double,返回它的二进制表示.如果该数字无法精确地用32位以内的二进制表示,返回"Error". ...
- AtCoder Regular Contest 128 部分题题解
关于鄙人罚坐两小时那件事...该开始看A题,这不就是个DP记录路径吗?Wrong了,嗯,我没用double,又Wrong,怎么回事,使劲检查自己的算法和细节问题,一个小时过去了,...这没错啊,又反复 ...