题意
给你 n 个字符串,选出最大的一个集合,满足两两之间不是对方的子串。
题解
AC自动机+最小路径可重复点覆盖方案=AC自动机+传递闭包+乱搞
求子串?KMP?这有我这种机智的人才会想到?AC自动机!
AC自动机是用来处理前缀的问题,看起来不适用,但它的fail指针太强大了!可以想象假设现在有一个串,它在trie树中以一条链的形式存储,从这条链中的每个点出去,扩展开来的就是它的一个子串。其实还是一个串后缀与一个串的前缀的匹配,其中后缀指的是一个串的部分,前缀要求是整串,那么就是AC自动机啦~~~
我们给一个串向它的子串连边。
注意到AC自动机只要求出一个即可,后面传递闭包。
因为选了路径开头的一个点,后面一个也不能选,所以接下来求一个最小路径可重复点覆盖方案。
最小路径可重复点覆盖方案就懒得写了,不会看这篇博客。
代码
#include<cstdio>
#include<string>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int END=10000000;
const int MAXN=760,MAXL=10000010;
int n;
string s[MAXN];
bool ma[MAXN][MAXN];
struct Tr{int id,fail,son[2];}tr[MAXL];int root=1,tot=1;//空间???
void insert(int id)
{
int x=root;
for(int i=0,len=s[id].length();i<len;i++)
{
int k=s[id][i]-'a';
if(!tr[x].son[k]) tr[x].son[k]=++tot;
x=tr[x].son[k];
}
tr[x].id=id;
}
int head,tail,q[MAXL];
void get_fail()
{
head=0,tail=0;
for(int k=0,x=root;k<2;k++) if(tr[x].son[k])
{
int y=tr[x].son[k];
tr[y].fail=root;
q[tail++]=y;if(tail==END) tail=0;
}
while(head!=tail)
{
int x=q[head++];if(head==END) head=0;
if(!tr[x].id) tr[x].id=tr[tr[x].fail].id;//debug
for(int k=0;k<2;k++) if(tr[x].son[k])
{
int y=tr[x].son[k],p=tr[x].fail;
while(p!=root && !tr[p].son[k])p=tr[p].fail;
if(tr[p].son[k]) tr[y].fail=tr[p].son[k];
else tr[y].fail=root;
q[tail++]=y;if(tail==END) tail=0;
}
}
}
void solve(int id)
{
int x=root;
for(int i=0,len=s[id].length();i<len;i++)
{
int k=s[id][i]-'a';
x=tr[x].son[k];
if(tr[x].id!=id) ma[id][tr[x].id]=true;//id->tr[x].id
else ma[id][tr[tr[x].fail].id]=true;
}
}
int T=0,vis[MAXN];
int match[MAXN];
bool succ[MAXN];
bool dfs(int x)
{
for(int y=1;y<=n;y++) if(ma[x][y] && vis[y]!=T)
{
vis[y]=T;
if(!match[y] || dfs(match[y])) return match[y]=x,true;
}
return false;
}
int li[MAXN];
int main()
{
// freopen("string.in","r",stdin);
// freopen("string.out","w",stdout);
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
cin>>s[i];
insert(i);
}
get_fail();
for(int i=1;i<=n;i++) solve(i);
for(int k=1;k<=n;k++)
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++) ma[i][j]|=ma[i][k]&ma[k][j];
/*debug
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++) printf("%d",ma[i][j]);
puts("");
}
// */
int ans=n;
for(int i=1;i<=n;i++)
{
T++;
if(dfs(i)) ans--,succ[i]=true;
}
printf("%d\n",ans);
for(int i=1,cnt=0;i<=n;i++) if(!succ[i]) li[++cnt]=i;
T++;bool modify=true;
while(modify)
{
modify=false;
for(int i=1;i<=ans;i++)
for(int j=1;j<=n;j++) if(ma[li[i]][j]) vis[j]=T;
for(int i=1;i<=ans;i++)
if(vis[li[i]]==T)
{
modify=true;
while(vis[li[i]]==T) li[i]=match[li[i]];
}
}
for(int i=1;i<=ans;i++) printf("%d ",li[i]);putchar('\n');
return 0;
}