@codeforces - 590E@ Birthday
@description@
给定 n 个互不相同的只包含 'a', 'b' 的字符串。
请选出最多的字符串,使得字符串两两之间没有包含关系(即不存在两个字符串 s, t 使得 s 是 t 的子串)。
输出方案。
Input
第一行一个整数 n (1 ≤ n ≤ 750) 表示字符串个数。
接下来 n 行每行一个只包含 'a', 'b' 的字符串。保证 n 个字符串总长不超过 10^7。
Output
第一行输出选出的字符串个数。
第二行输出你选择的字符串的编号,任意方案皆可。
Examples
Input
5
abab
aba
aabab
ababb
bab
Output
2
2 5
Note
3, 4 也是一种合法方案。
@solution@
注意包含关系其实是偏序关系,互相不包含的最大集合其实就是最长反链。
先考虑怎么得到串之间的偏序关系。因为子串总是前缀的某个后缀,所以每一个串向它所有前缀最长出现的后缀连边,最后利用传递性跑个传递闭包即可。
我们考虑对所有串建出 AC 自动机来辅助我们找到某个前缀 S 的最长出现后缀,这个可以通过 AC 自动机的 fail 树向下传递沿着 fail 边跳第一个遇到的完整的串编号实现。
最后再把所有串拿到这个 AC 自动机上跑一跑即可。但是注意某个串本身的最长出现后缀是它自身,这个时候需要沿着 fail 边跳一次再得到此时的最长出现后缀。
最长反链直接转为最小链覆盖然后二分图上跑一跑即可。但是重点在于,这道题还要求输出最长反链的方案。
搜了好久,终于在这篇博客里找到了我想要得到的答案。。。
我们考虑对二分图求出最大独立集,则对于每个点,如果它拆成的两个点都在独立集中,则将它加入反链。至于证明其实比较简单:
(1)首先这样构造出来的肯定是个合法的反链。
(2)假如原图中的点数为 n,最大匹配为 m。则最小链覆盖为 n - m,最大独立集为 2*n - m,拆成的点只有一个在独立集中的点 <= n,于是我们构造出来的反链长度 >= n - m;又因最小链覆盖总比反链长度大,所以有最大反链 <= n - m。于是得证。
那么问题转为怎么求二分图的最大独立集。因为最大独立集与最小点覆盖互为补集,我们考虑怎么去求最小点覆盖。
我们从 X 部的未匹配点出发,沿交错路径(即非匹配边 - 匹配边 - 非匹配边-...)访问点。最后得到的 X 部未访问点 + Y 部访问点即是最小点覆盖。
证明只需要证不存在一条边使得这条边的 X 部访问到且 Y 部未访问到即可,讨论一下这条边是匹配边还是非匹配边。
@accepted code@
#include<cstdio>
#include<queue>
#include<cstring>
#include<bitset>
#include<algorithm>
using namespace std;
const int MAXN = 750;
const int MAXS = int(1E7);
const int MAXV = 2*750;
const int MAXE = MAXV*MAXV + 2*MAXV;
const int INF = (1<<30);
struct FlowGraph{
struct edge{
int to, flow, cap;
edge *nxt, *rev;
}edges[MAXE + 5], *adj[MAXV + 5], *cur[MAXV + 5], *ecnt;
FlowGraph() {ecnt = &edges[0];}
void addedge(int u, int v, int c) {
edge *p = (++ecnt), *q = (++ecnt);
p->to = v, p->cap = c, p->flow = 0;
p->nxt = adj[u], adj[u] = p;
q->to = u, q->cap = 0, q->flow = 0;
q->nxt = adj[v], adj[v] = q;
p->rev = q, q->rev = p;
}
int d[MAXV + 5], s, t;
bool relabel() {
for(int i=s;i<=t;i++)
d[i] = t + 5, cur[i] = adj[i];
queue<int>que;
d[t] = 0; que.push(t);
while( !que.empty() ) {
int f = que.front(); que.pop();
for(edge *p=adj[f];p;p=p->nxt) {
if( p->rev->cap > p->rev->flow ) {
if( d[f] + 1 < d[p->to] ) {
d[p->to] = d[f] + 1;
que.push(p->to);
}
}
}
}
return !(d[s] == t + 5);
}
int aug(int x, int tot) {
if( x == t ) return tot;
int sum = 0;
for(edge *&p=cur[x];p;p=p->nxt) {
if( p->cap > p->flow && d[p->to] + 1 == d[x] ) {
int del = aug(p->to, min(tot - sum, p->cap - p->flow));
sum += del, p->flow += del, p->rev->flow -= del;
if( sum == tot ) return sum;
}
}
return sum;
}
int max_flow(int _s, int _t) {
s = _s, t = _t; int flow = 0;
while( relabel() )
flow += aug(s, INF);
return flow;
}
bool vis[MAXV + 5];
void dfs(int x) {
vis[x] = true;
for(edge *p=adj[x];p;p=p->nxt)
if( !vis[p->to] && p->cap > p->flow )
dfs(p->to);
}
}G;
int n, s, t;
bitset<MAXN + 5>e[MAXN + 5];
struct ACM{
struct node{
int ch[2], fail;
int tag;
}nd[MAXS + 5];
int ncnt, root;
ACM() {ncnt = root = 0;}
void add(char *S, int lenS, int x) {
int nw = root;
for(int i=0;i<lenS;i++) {
if( !nd[nw].ch[S[i] - 'a'] )
nd[nw].ch[S[i] - 'a'] = (++ncnt);
nw = nd[nw].ch[S[i] - 'a'];
}
nd[nw].tag = x;
}
void build() {
queue<int>que;
if( nd[root].ch[0] ) que.push(nd[root].ch[0]);
if( nd[root].ch[1] ) que.push(nd[root].ch[1]);
while( !que.empty() ) {
int f = que.front(); que.pop();
if( nd[f].ch[0] ) {
nd[nd[f].ch[0]].fail = nd[nd[f].fail].ch[0];
if( !nd[nd[f].ch[0]].tag ) nd[nd[f].ch[0]].tag = nd[nd[nd[f].ch[0]].fail].tag;
que.push(nd[f].ch[0]);
}
else nd[f].ch[0] = nd[nd[f].fail].ch[0];
if( nd[f].ch[1] ) {
nd[nd[f].ch[1]].fail = nd[nd[f].fail].ch[1];
if( !nd[nd[f].ch[1]].tag ) nd[nd[f].ch[1]].tag = nd[nd[nd[f].ch[1]].fail].tag;
que.push(nd[f].ch[1]);
}
else nd[f].ch[1] = nd[nd[f].fail].ch[1];
}
}
void solve(char *S, int lenS, int x) {
int nw = root;
for(int i=0;i<lenS;i++) {
if( nd[nw].tag )
e[x][nd[nw].tag] = true;
nw = nd[nw].ch[S[i] - 'a'];
}
nw = nd[nw].fail;
if( nd[nw].tag )
e[x][nd[nw].tag] = true;
}
}ac;
char S[MAXS + 5];
int len[MAXN + 5];
int main() {
scanf("%d", &n), s = 0, t = 2*n + 1;
for(int i=1;i<=n;i++) {
scanf("%s", S + len[i-1]);
len[i] = len[i-1] + strlen(S + len[i-1]);
ac.add(S + len[i-1], len[i] - len[i-1], i);
}
ac.build();
for(int i=1;i<=n;i++)
G.addedge(s, i, 1), G.addedge(n + i, t, 1);
for(int i=1;i<=n;i++)
ac.solve(S + len[i-1], len[i] - len[i-1], i);
for(int k=1;k<=n;k++)
for(int i=1;i<=n;i++)
if( e[i][k] ) e[i] |= e[k];
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if( e[i][j] ) G.addedge(i, j + n, 1);
printf("%d\n", n - G.max_flow(s, t));
G.dfs(G.s);
for(int i=1;i<=n;i++)
if( G.vis[i] && (!G.vis[i+n]) )
printf("%d ", i);
}
@details@
不要问为什么 O(n^3) 可以跑 750 的数据范围,问就是 bitset 优化传递闭包 + dinic 跑最大匹配 + codeforces 的机子。
一开始因为不熟悉 bitset(从来没用过 2333)还 WA 了几次。
题目给出的字符集只有 'a','b' 其实只是纯粹防止你内存开太大 MLE 而已。
顺便,dinic 求独立集有一个好,就是因为反向边的存在,可以直接判断是否满流然后跑 dfs。
@codeforces - 590E@ Birthday的更多相关文章
- Codeforces 590E - Birthday(AC 自动机+Dilworth 定理+二分图匹配)
题面传送门 AC 自动机有时只是辅助建图的工具,真的 首先看到多串问题,果断建出 AC 自动机.设 \(m=\sum|s_i|\). 不难发现子串的包含关系构成了一个偏序集,于是我们考虑转化为图论,若 ...
- python爬虫学习(5) —— 扒一下codeforces题面
上一次我们拿学校的URP做了个小小的demo.... 其实我们还可以把每个学生的证件照爬下来做成一个证件照校花校草评比 另外也可以写一个物理实验自动选课... 但是出于多种原因,,还是绕开这些敏感话题 ...
- 【Codeforces 738D】Sea Battle(贪心)
http://codeforces.com/contest/738/problem/D Galya is playing one-dimensional Sea Battle on a 1 × n g ...
- 【Codeforces 738C】Road to Cinema
http://codeforces.com/contest/738/problem/C Vasya is currently at a car rental service, and he wants ...
- 【Codeforces 738A】Interview with Oleg
http://codeforces.com/contest/738/problem/A Polycarp has interviewed Oleg and has written the interv ...
- CodeForces - 662A Gambling Nim
http://codeforces.com/problemset/problem/662/A 题目大意: 给定n(n <= 500000)张卡片,每张卡片的两个面都写有数字,每个面都有0.5的概 ...
- CodeForces - 274B Zero Tree
http://codeforces.com/problemset/problem/274/B 题目大意: 给定你一颗树,每个点上有权值. 现在你每次取出这颗树的一颗子树(即点集和边集均是原图的子集的连 ...
- CodeForces - 261B Maxim and Restaurant
http://codeforces.com/problemset/problem/261/B 题目大意:给定n个数a1-an(n<=50,ai<=50),随机打乱后,记Si=a1+a2+a ...
- CodeForces - 696B Puzzles
http://codeforces.com/problemset/problem/696/B 题目大意: 这是一颗有n个点的树,你从根开始游走,每当你第一次到达一个点时,把这个点的权记为(你已经到过不 ...
随机推荐
- hibernate 注解使用
实体类声明,需要引用 import javax.persistence.Entity; import javax.persistence.Table; @Entity @Table(name=&quo ...
- Linux 基础命令3 shell
echo 显示一行文本 各种展开的实例 波浪线展开 算术表达式展开 支持的运算 奇怪的花括号展开 花括号的..用法 花括号(任选一个)的嵌套 参数展开$符很重要哦(一种展开做另一种的参数) 命令的替换 ...
- CF1132G
听说,一个好的Oier都是题目喂出来的. 题目 定义一个序列的最长贪心严格上升子序列为:若选出的子序列为 \(a\),对于其中相邻两项 \(i,j\),不存在 b\(i<k<j\),满足在 ...
- 七牛云+MPic-图床神器搭建
1. 注册七牛云 2. 新建存储空间 3. 密钥 4. 记录自己账户四个值: 测试域名:xxxxx.xx.clouddn.com ak:xxxxxxxxxxxxxxxxxxxx sk:xxxxxxxx ...
- web前端学习(三)css学习笔记部分(10)-- 初级开发工程师面试题
html所有基础.h5的所有基础.CSS所有常用的布局.CSS3新增的属性等等内容 附一个课程上用到的链接(转载): http://www.zhufengpeixun.cn/JavaScriptmia ...
- Hdu 1068 最小路径覆盖
Girls and Boys Time Limit: 20000/10000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others) ...
- 从0开始学习 GitHub 系列之「03.Git 速成」
前面的 GitHub 系列文章介绍过,GitHub 是基于 Git 的,所以也就意味着 Git 是基础,如果你不会 Git ,那么接下来你完全继续不下去,所以今天的教程就来说说 Git ,当然关于 G ...
- excel怎么并排查看两个工作表
excel怎么并排查看两个工作表 excel怎么并排查看两个工作表?excel打开一个窗口想要同时查看两个工作表中的数据,类似于word中的分栏效果,该怎么实现呢?EXCEL是一个使用最多的办公软件, ...
- 【JZOJ4161】于神之怒 莫比乌斯反演
任务 答案mod 1e9+7. 解法 容易写出反演: Ans=∑T=1nTk∗∑i=1⌊nT⌋⌊niT⌋⌊miT⌋μ(i) ∑⌊nT⌋i=1⌊niT⌋⌊miT⌋μ(i)这个因式显然是经典的分块处理: ...
- Linux下下载安装jdk1.7和IDEA
一.安装JDK1.7 准备: 到Oracle官网下载下载jdk1.7,参考博客 https://blog.csdn.net/H12KJGJ/article/details/79240984 官网地址: ...