luogu P2322 [HNOI2006] 最短母串问题
思路比较的简单的 dp 题。
首先看数据范围,\(n \leqslant 12,len\leqslant50\) 应该是状压没跑了。
考虑设 \(f_{i,j}\) 表示在选择集合 \(j\) 且,尾部的字符串为第 \(i\) 个的最短长度。
考虑两个字符串重合的部分,设 \(len_{i,j}\) ,表示 \(i\) 在前,\(j\) 在后时的最长公共前后缀长度,这个可以用 \(KMP\) 预处理出来。注意在两字符串相加时,为了防止匹配超过原长,可以在两字符串之间加一个奇怪的字符,以防止匹配错误。
然后状态转移方程显然。
\[f_{k,j|(1<<(k-1)} = \min (f_{i,j}+size_k-len_{i,k})
\]
至于如何输出字典序最小的串,可以设 \(g_{i,j}\) 表示在当前状态下的长度最短的字典序最小的串,和 \(f_{i,j}\) 一起转移就行了。
一些细节,可以先用 AC自动机 去重,去除串与串之间包含的情况。
复杂度 \(O(n^22^n)\)
代码
#include <iostream>
#include <cstdio>
#include <queue>
#include <cmath>
#include <vector>
#include <cstring>
#include <algorithm>
const int M=100000;
const int N=15;
const int L=60;
const int inf=2e9;
using namespace std;
inline int read() {
int f=1,x=0;
char ch=getchar();
while(ch>'9' || ch<'0') {
if(ch=='-') {
f=-1;
}
ch=getchar();
}
while(ch>='0' && ch<='9') {
x=(x<<3)+(x<<1)+ch-'0';
ch=getchar();
}
return f*x;
}
int n,cnt,tot;
int le[N][N],nxt[L<<1],l[N];
int f[N][(1<<N)];
string s[N],a[N],g[N][1<<N],ans[M];
struct Tire {
int fail,end;
int son[26];
}tr[M];
bool vis[N];
void insert(string ss,int id) {
int len=ss.size(),now=0;
for(int i=0;i<len;i++) {
int ch=ss[i]-'A';
if(!tr[now].son[ch]) {
tr[now].son[ch]=++cnt;
}
now=tr[now].son[ch];
}
if(!tr[now].end) tr[now].end=id;
else vis[id]=1;
}
void get_fail() {
queue <int> q;
for(int i=0;i<26;i++) {
if(tr[0].son[i]) {
q.push(tr[0].son[i]);
}
}
while(!q.empty()) {
int now=q.front();
q.pop();
for(int i=0;i<26;i++) {
int v=tr[now].son[i];
if(v) {
tr[v].fail=tr[tr[now].fail].son[i];
q.push(v);
}
else {
tr[now].son[i]=tr[tr[now].fail].son[i];
}
}
}
}
void query(string ss,int id) {
if(vis[id]) return;
int len=ss.size(),now=0;
for(int i=0;i<len;i++) {
int ch=ss[i]-'A';
now=tr[now].son[ch];
int tmp=now;
while(tmp) {
vis[tr[tmp].end]=1;
tmp=tr[tmp].fail;
}
}
vis[id]=0;
}
int KMP(string ss) {
int len=ss.size();
for(int i=0;i<=len;i++) {
nxt[i]=0;
}
for(int i=2,j=0;i<len;++i) {
while(j && ss[j+1]!=ss[i]) j=nxt[j];
if(ss[j+1]==ss[i]) j++;
nxt[i]=j;
}
return nxt[len-1];
}
void init() {
get_fail();
for(int i=1;i<=n;i++) query(s[i],i);
for(int i=1;i<=n;i++) {
if(!vis[i]) a[++tot]=s[i];
}
n=tot;
for(int i=1;i<=n;i++) l[i]=a[i].size();
for(int i=1;i<=n;i++) {
for(int j=1;j<=n;j++) {
if(i==j) continue;
string ss=" "+a[j]+"|"+a[i];
le[i][j]=KMP(ss);
}
}
}
int main() {
n=read();
for(int i=1;i<=n;i++) {
cin>>s[i];
insert(s[i],i);
}
init();
memset(f,0x3f,sizeof(f));
for(int i=1;i<=n;i++) {
f[i][(1<<(i-1))]=l[i];
g[i][(1<<(i-1))]=a[i];
}
f[0][0]=0;
for(int i=1;i<(1<<n);i++) {
for(int j=1;j<=n;j++) {
if(((1<<(j-1)) & i)) {
for(int k=1;k<=n;k++) {
if(!((1<<(k-1)) & i)) {
int len=le[j][k],to=i|(1<<(k-1));
string ss=g[j][i]+a[k].substr(len,l[k]);
if((f[j][i]+l[k]-len < f[k][to]) ||
(f[j][i]+l[k]-len==f[k][to] && g[k][to]>ss)) {
f[k][to]=f[j][i]+l[k]-len;
g[k][to]=ss;
}
}
}
}
}
}
int mi=inf,sum=0;
for(int i=1;i<=n;i++) mi=min(mi,f[i][(1<<n)-1]);
for(int i=1;i<=n;i++) {
if(mi==f[i][(1<<n)-1]) {
ans[++sum]=g[i][(1<<n)-1];
}
}
sort(ans+1,ans+sum+1);
cout<<ans[1];
return 0;
}