题意

给定 \(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的更多相关文章

  1. IOI 2020 集训队作业胡扯

    首先安慰自己:做的没集训队快很正常-- 很正常-- 做不完也很正常-- 很正常-- 全都不会做也很正常-- 很正常-- 表格 试题一 完成情况 试题二 完成情况 试题三 完成情况 cf549E cf6 ...

  2. IOI 2020 国家集训队作业

    \(\checkmark\) 试题一 完成情况 试题二 完成情况 试题三 完成情况 cf549E cf674G arc103_f \(\checkmark\) cf594E agc034_f agc0 ...

随机推荐

  1. php json_encode()函数返回对象和数组问题

    php json_encode() 函数格式化数据时会根据不同的数组类型格式化不同类型的json数据 索引数组时 <?php $arr = [1,2,3,4,5]; print_r(json_e ...

  2. 树莓派手动设置静态IP和DNS方法

    在使用树莓派的过程中,往往需要手动设置一个静态的IP地址,一来可以防止DHCP自动分配的IP变动,二来可提高树莓派的网络连接速度.查看官方文档 man dhcpcd.conf可知,需要配置静态IP的话 ...

  3. 简单记录一次注入到shell

    0x00 前言 帮朋友之前拿的一个站,有点久了没有完整截图,简单记录一下. 0x01 基础信息 操作系统:win 集成环境:phpstudy 端口开放:82,3306,3389 有phpmyadmin ...

  4. pycharm git 用法总结

    一.配置git 二.登录GitHub账号 三.创建git respository 四.提交文件 五.共享给GitHub 六.修改文件push到版本库 七.从版本库checkout 项目

  5. Vue指令之`v-bind`的三种用法及v-on事件指令

    v-bind:是 Vue中,提供的用于绑定属性的指令 1. 直接使用指令`v-bind` 2. 使用简化指令`:` 3. 在绑定的时候,拼接绑定内容:`:title="btnTitle + ...

  6. Android面试题 请解释下单线程模型中Message、Handler、MessageQueue、Looper之间的关系

    简单的说,Handler获取当前线程中的looper对象,looper用来存放从MessageQueue中取出的Message,再由Handler进行Message分发和处理,按照先进先出执行. Me ...

  7. Android笔记(三十一)Android中线程之间的通信(三)子线程给主线程发送消息

    先看简单示例:点击按钮,2s之后,TextView改变内容. package cn.lixyz.handlertest; import android.app.Activity; import and ...

  8. Linux 之 搜索

    locate - 文件名搜索命令 用于查找文件 格式为:locate 文件名 该命令用于查找符合条件的文件,它会去保存文件与目录名称的数据库内,查找合乎范本样式条件的文件或目录. 因为该命令是直接在数 ...

  9. [postman][API 测试]用Postman做RestAPI测试学习笔记

    痛点:最近有个API网关的兼容性测试任务,需要验证API是否可用,返回值符合预期,如果手工复制粘贴curl命令,繁琐且低效 调研时发现了Postman 这个chrom插件,试用了2天后发现使用起来很方 ...

  10. 使用 rem 设置文字大小

    一.那到底什么是 rem 呢? 规范中明确写道: Equal to the computed value of ‘font-size’ on the root element. 「rem」是指根元素( ...