分析
发现这个是与字串有关系的,就考虑后缀自动机和 自动机。我们发现对于这个问题后缀自动机使用会非常的不自然,因为我们并没有一个原串。我们就考虑如何用
自动机解决。我们可以把所有的目标串插入自动机。因为
所以结尾并没有太多状态,可以考虑状压
。令
为考虑到节点
,现在状态为
的最小串。那么我们就只需要在
自动机上做转移就可以了,在构建
指针时,因为
指向的是当前串的最大后缀。所以我们有了第一个转移
。那么我们做一次
就可以满足长度最短,从小到大枚举字母就可以满足字典序最小这个条件。那么当我们找到第一个
其中
。那么证明它满足了所以串,这个时候递归输出就好了。每个节点的输出可以考虑数组模拟链表处理,否则空间复杂度可能过不去。
代码
#include<bits/stdc++.h>
using namespace std;
const int N = 2e3 + 100,M = 1e7 + 10;
int fail[N],ch[N][26],fa[M],A[M],f[N];
int size,n;
char S[N];
bool vis[N][20005];
#define P pair<int,int>
void insert(char *a,int id) {
int len = strlen(a);int u = 0;
for(int i = 0;i < len;i++) {
int c = a[i] - 'A';
if(!ch[u][c]) ch[u][c] = ++size;
// cout <<a[i]<<" now: " << ch[u][c] << endl;
u = ch[u][c];
}
f[u] |= (1<<id-1);
}
void build() {
queue<int> q;
for(int i = 0;i < 26;i++) if(ch[0][i]) q.push(ch[0][i]);
while(!q.empty()) {
int u = q.front();q.pop();
for(int i = 0;i < 26;i++) {
int v = ch[u][i];
if(v) {
fail[v] = ch[fail[u]][i];
f[v] |= f[ch[fail[u]][i]];q.push(v);
}
else ch[u][i] = ch[fail[u]][i];
}
}
// for(int i = 1;i <= size;i++) cout << fail[i] << " " << f[i] << endl;
}
void print(int x) {
if(fa[x]) print(fa[x]);
printf("%c",A[x]+'A');
}
void solve() {
queue<P> q;int xzc = 0,Id = 0;
vis[0][0] = 1;q.push(P(0,0));
while(!q.empty()) {
P x = q.front();q.pop();
if(x.second == (1<<n)-1) {
print(Id);printf("\n");
return;
}
for(int i = 0;i < 26;i++) {
int v = ch[x.first][i];
int state = x.second|f[v];
if(vis[v][state]) continue;
vis[v][state] = 1;
fa[++xzc] = Id;A[xzc] = i;
q.push(P(v,state));
}
Id++;
}
}
int main() {
scanf("%d",&n);
for(int i = 1;i <= n;i++) {
scanf("%s",S);insert(S,i);
}
build();
solve();
return 0;
}
京公网安备 11010502036488号