分析:
一看到这道题,就知道是AC自动机上的dp
于是建出了AC自动机,构造好了fail指针
设计状态:f[i][j],表示第i位走到了AC自动机上的第j个点
但是在转移的时候出了问题:有可能会出现一个单词没有完全出现就跳到了ta的fail指针上
于是我就在维护fail指针的时候特判了一下
但是又出现了一个问题:有可能一个单词多次出现(n个单词没有都出现)的情况
这只能说明一个问题:设计的状态有问题
我们需要重新设计一个状态,解决以上两个问题:
因为构造的密码中一定会出现所有的n个模式串
显然,如果
s
是
因此我们可以在构建AC自动机之前就合并两个字符串
这样不仅可以避免错误,还可以缩小数据范围
直接n^2枚举所有字符串,如果某个子串是其他串的子串,我们就打上标记
只有没有标记的串才会在AC自动机中出现
一开始我直接暴力判断,这样单次比较的复杂度应该是O(n),但是我们要进行n^2次比较
这里涉及到了一个小知识点:判断字符串的包含关系
int check(int x,int y)
{
for (int i=min(len[x],len[y]);i>=0;i--)
{
bool ff=0;
for (int j=0;j<i;j++)
if (s[x][len[x]-i+j]!=s[y][j])
{
ff=1;
break;
}
if (!ff) return i; //返回的是相同字符串的长度
}
}
为了防止一个单词多次出现
我们需要记录一下每个单词的出现情况
设计状态:f[i][j][k]
表示第i位,到达AC自动机上的结点j,单词的出现情况为k
但是这样空间是承受不了的
然而我们发现,第i位的转移只会来自第i-1位
我们可以用滚动数组优化
具体转移的时候,我们只要枚举i,j,k以及j的儿子,
用f[i][j][k]转移f[i+1][son][k|son]即可
至于输出方案,
实际上若字符串的某一个位不属于n个字符串中的任何一个,则方案数必然>42,
所以这必然是由n个字符串通过某种排列顺序紧凑排列后得到的
我们只要枚举字符串的排列顺序后按照字典序输出即可
#include<cstdio>
#include<cstring>
#include<iostream>
#include<queue>
#include<algorithm>
#define ll long long
using namespace std;
const int N=110;
char s[30][15],c[20],a[50][20];
int top,L,n,ch[N][27],fail[N],ed[N],len[N],o=0,tot=0,rak[45];
ll f[2][N][30000];
bool del[11],vis[11];
void build(int bh)
{
int now=0;
for (int i=0;i<strlen(s[bh]);i++)
{
int x=s[bh][i]-'a';
if (!ch[now][x]) ch[now][x]=++top;
now=ch[now][x];
}
ed[now]=1<<(bh-1); //记录结点属于的单词
}
void make_fail()
{
queue<int> Q;
for (int i=0;i<26;i++)
if (ch[0][i]) Q.push(ch[0][i]);
while (!Q.empty())
{
int now=Q.front(); Q.pop();
for (int i=0;i<26;i++)
{
if (!ch[now][i])
{
if (ed[now]) ch[now][i]=ch[fail[now]][i];
continue;
}
fail[ch[now][i]]=ch[fail[now]][i];
ed[ch[now][i]]|=ed[fail[ch[now][i]]];
Q.push(ch[now][i]);
}
}
}
void dfs(int x,int lst)
{
if (x>o)
{
if (lst!=L) return;
++tot;
memcpy(a[tot],c,sizeof(a[tot]));
return;
}
for (int i=1;i<=n;i++)
if (!vis[i]&&!del[i])
{
vis[i]=1;
int le=len[i],k;
for (int j=max(lst-le,0);j<lst;j++)
{
for (k=0;k<le&&k+j<lst;k++)
if (s[i][k]!=c[j+k]) break;
int tmp=lst;
if (k==le||k+j==lst)
{
for (;k<le&&tmp<L;k++)
c[tmp++]=s[i][k];
if (k==le) dfs(x+1,tmp);
}
}
if (lst+le<=L)
{
for (k=0;k<le;k++)
c[lst+k]=s[i][k];
dfs(x+1,lst+le);
}
vis[i]=0;
}
}
int cmp(int x,int y)
{
for (int i=0;i<L;i++)
if (a[x][i]<a[y][i])
return 1;
else if (a[x][i]>a[y][i])
return 0;
return 0;
}
void print()
{
dfs(1,0);
for (int i=1;i<=tot;i++) rak[i]=i;
sort(rak+1,rak+1+tot,cmp);
for (int i=1;i<=tot;i++)
printf("%s\n",a[rak[i]]);
}
void solve()
{
f[0][0][0]=1;
int now=0;
for (int i=0;i<L;i++)
{
int lst=now; now^=1;
memset(f[now],0,sizeof(f[now]));
for (int j=0;j<=top;j++)
for (int k=0;k<(1<<n);k++) if (f[lst][j][k])
for (int l=0;l<26;l++)
f[now][ch[j][l]][k|ed[ch[j][l]]]+=f[lst][j][k];
}
int tt=0; //只有未被删除的字符串才在AC自动机中出现
for (int i=1;i<=n;i++) if (!del[i]) tt+=(1<<(i-1)),o++;
ll ans=0;
for (int i=0;i<=top;i++)
ans+=f[L&1][i][tt];
printf("%lld\n",ans);
if (ans<=42) print();
}
int check(int x,int y)
{
for (int i=min(len[x],len[y]);i>=0;i--)
{
bool ff=0;
for (int j=0;j<i;j++)
if (s[x][len[x]-i+j]!=s[y][j])
{
ff=1;
break;
}
if (!ff) return i;
}
}
int main()
{
scanf("%d%d",&L,&n);
for (int i=1;i<=n;i++)
scanf("%s",s[i]),len[i]=strlen(s[i]);
for (int i=1;i<=n;i++) if (!del[i]) //去重
for (int j=1;j<=n;j++) if (i!=j)
if (check(i,j)==len[j])
del[j]=1;
for (int i=1;i<=n;i++) if (!del[i]) build(i);
make_fail();
solve();
return 0;
}