CF590E Birthday
题意
给定 \(n\) 个只由 \(a,b\) 组成的字符串,保证两两不同。
要求从中选出尽可能多的字符串,使得选出的字符串中,任意一个字符串不是另一个的子串。
求最多能选多少并输出一个可行解。
\(n \leq 750, \sum |S_i| \leq 10^6\)
思路
考虑根据包含关系建边,可以得到一张有向无环图,之后我们要求的是一个最大点集,两两不能到达。这就是祭祀。
即求最长反链,然后转化为最小链覆盖,再到传递闭包后的最大匹配,关于证明,ta没了。
接着来说一下如何输出方案。
在求完传递闭包之后,能互相到达的点一定是相邻的,所以最长反链即为最大独立集。
定义:最小点覆盖就是选择最少的点来覆盖所有的边。一个点能覆盖以它为端点的边
最大独立集=所有顶点数-最小点覆盖
突然写不下去了,就看这个吧偷懒,啥时候空了我来补
然后来考虑怎么建图。
由于是子串问题,很容易想到用AC自动机。
但是如果把所有包含关系都求出来,妥妥的T了
对于三个串 \(a,b,c\),如果 \(a\)包含\(b\) , \(b\)包含\(c\), 那么\(a\)包含\(c\)。
回忆AC自动机匹配的过程,对于文本串\(i\)每匹配到一个字符位置\(u\)就跳\(fail\),考虑到每次第一个跳到的一定是最长的,且每次跳的都是前面所有的子串,所以我们只要将\(i\)与\(fail[u]\)上包含的最长完整串连边就行。注意\(fail[u]\)上可能不存在完整串末尾节点,那么我们就继承离他最近的\(fail\)树祖先上的串。
不要忘记\(u\)本身是末尾节点的情况。
至于怎么继承,只要在\(get \_ fail\)中加一句就好了
#include <bits/stdc++.h>
using std::string;
using std::queue;
queue <int> q;
const int N=755,M=10000005;
string s[N];
int ch[M][2],val[M],fail[M],cnt,n,vis[N],now,to[N],len[N],f[N];
int tagl[N],tagr[N];
bool a[N][N];
void insert(string s,int l,int id){
int u=0;
for (int i=0;i<l;i++){
int c=s[i]-'a';
if (!ch[u][c]) ch[u][c]=++cnt;
u=ch[u][c];
}
val[u]=id;
}
void get_fail(){
if (ch[0][0]) q.push(ch[0][0]);
if (ch[0][1]) q.push(ch[0][1]);
while (!q.empty()){
int x=q.front();
if (!val[x]) val[x]=val[fail[x]];
q.pop();
for (int i=0;i<2;i++)
if (ch[x][i]) {
q.push(ch[x][i]);
fail[ch[x][i]]=ch[fail[x]][i];
}else ch[x][i]=ch[fail[x]][i];
}
}
void match(string s,int l,int id){
int u=0;
for (int i=0;i<l;i++){
int c=s[i]-'a';
u=ch[u][c];
if (val[u]) a[id][val[u]]=1;
if (val[fail[u]]) a[id][val[fail[u]]]=1;
}
}
int dfs(int x){
for (int i=1;i<=n;i++)
if (a[x][i] && vis[i]!=now){
vis[i]=now;
if (f[i]==0 || dfs(f[i])){
to[x]=i,f[i]=x;
return 1;
}
}
return 0;
}
void dfs2(int x){
tagl[x]=1;
for (int i=1;i<=n;i++){
if (a[x][i]==0) continue;
if (!tagr[i]){
tagr[i]=1;
dfs2(f[i]);
}
}
}
int main(){
scanf("%d",&n);
for (int i=1;i<=n;i++){
std::cin>>s[i];
len[i]=s[i].length();
insert(s[i],len[i],i);
}
get_fail();
for (int i=1;i<=n;i++)
match(s[i],len[i],i);
for (int k=1;k<=n;k++)
for (int i=1;i<=n;i++)
for (int j=1;j<=n;j++)
a[i][j]=a[i][j]|(a[i][k] & a[k][j]);
for (int i=1;i<=n;i++) a[i][i]=0;
int ans=0;
for (int i=1;i<=n;i++)
now=i,ans+=dfs(i);
printf("%d\n",n-ans);
for (int i=1;i<=n;i++)
if (!to[i]) dfs2(i);
for (int i=1;i<=n;i++)
if (tagr[i]==0 && tagl[i]) printf("%d ",i);
}
后记
追随神仙的脚步
争取做到\(\frac{1}{1 page}\)
CF590E Birthday的更多相关文章
- IOI 2020 集训队作业胡扯
首先安慰自己:做的没集训队快很正常-- 很正常-- 做不完也很正常-- 很正常-- 全都不会做也很正常-- 很正常-- 表格 试题一 完成情况 试题二 完成情况 试题三 完成情况 cf549E cf6 ...
- IOI 2020 国家集训队作业
\(\checkmark\) 试题一 完成情况 试题二 完成情况 试题三 完成情况 cf549E cf674G arc103_f \(\checkmark\) cf594E agc034_f agc0 ...
随机推荐
- netcore使用EFcore(第一个实例)
说明:搭建netcore 使用efcore入门教程,跟着这个教程,傻瓜都可以成功!O(∩_∩)O哈哈~,咱们开始吧: 首先介绍下环境: vs2017, netcore2.2, EntityFramew ...
- MySQL LAST_INSERT_ID()用法
last_insert_id()函数是适用于id为自动生成的表 下面是插入表数据时last_insert_id()函数的两种用法: 表结构: 此表使用last_insert_id()函数的字段为par ...
- Java内存模型之分析volatile
前篇博客[死磕Java并发]—–深入分析volatile的实现原理 中已经阐述了volatile的特性了: volatile可见性:对一个volatile的读,总可以看到对这个变量最终的写: vola ...
- springboot启动流程(目录)
springboot出现有段时间了,不过却一直没有怎么去更多地了解它.一方面是工作的原因,另一方面是原来觉得是否有这个必要,但要持续做java似乎最终逃不开要去了解它的命运.于是考虑花一段时间去学习一 ...
- vue 鼠标移入移出事件执行多次(尤其ie)
来自:https://www.cnblogs.com/myfirstboke/p/9150809.html 侵删 <p @mouseover="over($event)" ...
- js中的深复制与浅复制
前言 所谓深复制与浅复制(深拷贝与浅拷贝),乍一听感觉听高大上,像是一个非常难理解的概念,其实我们平常项目开发都是在用的,只是你可能不知道该怎么叫它的名字而已,就像你听熟了一首歌,就是不知道这首歌叫什 ...
- CentOS7安装CDH 第十一章:离线升级CDH版本
相关文章链接 CentOS7安装CDH 第一章:CentOS7系统安装 CentOS7安装CDH 第二章:CentOS7各个软件安装和启动 CentOS7安装CDH 第三章:CDH中的问题和解决方法 ...
- Linux 之 搜索
locate - 文件名搜索命令 用于查找文件 格式为:locate 文件名 该命令用于查找符合条件的文件,它会去保存文件与目录名称的数据库内,查找合乎范本样式条件的文件或目录. 因为该命令是直接在数 ...
- Win10安装MySQL5.7版本 解压缩版方法
1.下载地址:https://dev.mysql.com/downloads/mysql/5.7.html#downloads 直接点击下载项 下载后: 2.可以把解压的内容随便放到一个目录,我的是如 ...
- es6中的Object.assign
在写一些插件的时候,我们会经常遇到所传参数需要合并默认参数,并覆盖相同参数的情况,在jQuery中我们可以使用$.extend(),在原生中要想使用得自己封装, 但自从es6出现了Object.ass ...