Description

给定n个字符串(S1,S2,„,Sn),要求找到一个最短的字符串T,使得这n个字符串(S1,S2,„,Sn)都是T的子串。

32MB

Input

第一行是一个正整数n(n<=12),表示给定的字符串的个数。
以下的n行,每行有一个全由大写字母组成的字符串。每个字符串的长度不超过50.

Output

只有一行,为找到的最短的字符串T。在保证最短的前提下,
如果有多个字符串都满足要求,那么必须输出按字典序排列的第一个。

Sample Input

2
ABCD
BCDABC

Sample Output

ABCDABC
 

Solution

一看是一个AC自动机。

一看是一个状压。

一看AC自动机节点再记录一个has包含的字符串集合。

一看要输出方案,肯定也要先考虑怎么弄出最短的长度。

f[i][(1<<n)-1]表示,匹配到AC自动机上的i点,包含的字符串集合为。。。的最短长度。

一看转移有环,然后无法再加入新的阶段,因为会MLE会TLE

所以要环形处理。

一看是一个取min的do

所以可以考虑最短路。

dij,spfa复杂度卡不过。

一看边权只有1……

BFS大法吼!

长度OK

怎么处理方案?

ywy_c_asm:

一遍bfs求出最短距离len,然后再一遍dfs找方案。

dfs时,相当于再把bfs的最短路怎么来的再访问一遍。如果dis[y]=dis[x]+1那么可以转移的,才可以访问。

还需要知道一个点到终点的最短路。

(反向多起点BFS???不行,或运算不可逆)

我们dfs时就可以实现的。类似树形dp

然后如果一个点到一个(1<<n)-1状态的点距离为juli的话,如果有dis[x]+juli[x]==len,那么,这次选择的这个y,所填的字符,就是最终答案的一个字符。

直接加入答案字符串。

char从A~Z枚举。保证第一次搜到的是字典序最小的。

而且一定是连续加入ans字符串。

dfs开头放上,如果tot==n return;

代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=;
const int M=;
const int U=**((<<)-)+;
const int inf=0x3f3f3f3f;
int n;
char s[];
struct trie{
int fail[M],ch[M][];
int has[M];
int cnt;
void ins(char *s,int l,int id){
int now=;
for(int i=;i<=l;i++){
int x=s[i]-'A';
if(!ch[now][x]) ch[now][x]=++cnt;
now=ch[now][x];
}
has[now]|=(<<(id-));
}
void build(){
queue<int>q;
for(int i=;i<=;i++){
if(ch[][i]) fail[ch[][i]]=,q.push(ch[][i]);
}
while(!q.empty()){
int x=q.front();q.pop();
has[x]|=has[fail[x]];
for(int i=;i<=;i++){
if(ch[x][i]){
fail[ch[x][i]]=ch[fail[x]][i];
q.push(ch[x][i]);
}
else ch[x][i]=ch[fail[x]][i];
}
}
}
}ac;
int get(int ptr,int st){
return ptr*(<<n)+st;
}
int dis[U];
bool vis[U];
struct node{
int P,S;
};
queue<node>q;
void bfs(){ memset(dis,0x3f,sizeof dis);
int str=get(,);
dis[str]=;
vis[str]=;
node nn;nn.P=,nn.S=;
q.push(nn);
while(!q.empty()){
node lp=q.front();q.pop();
for(int i=;i<=;i++){
int to=ac.ch[lp.P][i];
int NS=lp.S|ac.has[ac.ch[lp.P][i]];
int NID=get(to,NS);
if(!vis[NID]){
dis[NID]=dis[get(lp.P,lp.S)]+;
vis[NID]=;
node kk;
kk.P=to;kk.S=NS;
q.push(kk);
}
}
}
}
int len;
int tot;
char ans[M];
int juli[U];
void dfs(int ptr,int st){ int now=get(ptr,st);
juli[now]=inf; if(tot==len) return; if(st==(<<n)-) {
juli[now]=;return;
}
for(int i=;i<=;i++){
int to=ac.ch[ptr][i];
int NS=st|ac.has[to];
int NID=get(to,NS);
if(dis[NID]==dis[now]+){
if(!vis[NID]){
vis[NID]=;
dfs(to,NS);
}
juli[now]=min(juli[now],juli[NID]+);
if(dis[now]+juli[now]==len){
ans[++tot]='A'+i;return;
}
}
}
}
int main(){
scanf("%d",&n);
for(int i=;i<=n;i++){
scanf("%s",s+);
int l=strlen(s+);
ac.ins(s,l,i);
}
ac.build();
bfs();
len=inf;
//for(int j=0;j<=(1<<n)-1;j++)
//for(int i=0;i<=ac.cnt;i++){
// cout<<i<<" "<<j<<" : "<<dis[get(i,j)]<<endl;
//}
for(int i=;i<=ac.cnt;i++){
int id=get(i,(<<n)-);
len=min(len,dis[id]);
}
//cout<<" len "<<len<<endl;
memset(vis,,sizeof vis);
memset(juli,0x3f,sizeof juli);
vis[get(,)]=;
dfs(,);
//int haha=dfs(0,0);
for(int i=tot;i>=;i--){
printf("%c",ans[i]);
}
return ;
}

1

但是不够优美。

为什么要bfs然后再dfs呢?

bfs也可以求前驱啊!!
bfs时,第一更新到的就是最短路。

如果我们char A~Z,那么更新到的char

也就叫from[y],也就是到y这个点所形成的字典序最小字符串最后一个字符。

记录from,pre(也就是前驱)

bfs后,先找到len

再把所有f[i][(1<<n)-1]的字符找出来,cmp一下。

反正复杂度不超过600*600

代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=;
const int M=;
const int U=**((<<)-)+;
const int inf=0x3f3f3f3f;
int n;
char s[];
struct trie{
int fail[M],ch[M][];
int has[M];
int cnt;
void ins(char *s,int l,int id){
int now=;
for(int i=;i<=l;i++){
int x=s[i]-'A';
if(!ch[now][x]) ch[now][x]=++cnt;
now=ch[now][x];
}
has[now]|=(<<(id-));
}
void build(){
queue<int>q;
for(int i=;i<=;i++){
if(ch[][i]) fail[ch[][i]]=,q.push(ch[][i]);
}
while(!q.empty()){
int x=q.front();q.pop();
has[x]|=has[fail[x]];
for(int i=;i<=;i++){
if(ch[x][i]){
fail[ch[x][i]]=ch[fail[x]][i];
q.push(ch[x][i]);
}
else ch[x][i]=ch[fail[x]][i];
}
}
}
}ac;
int get(int ptr,int st){
return ptr*(<<n)+st;
}
int dis[U];
bool vis[U];
struct node{
int P,S;
};
queue<node>q;
int pre[U];
int from[U];
void bfs(){
memset(dis,0x3f,sizeof dis);
int str=get(,);
dis[str]=;
vis[str]=;
pre[str]=-;//warning!!
node nn;nn.P=,nn.S=;
q.push(nn);
while(!q.empty()){
node lp=q.front();q.pop();
for(int i=;i<=;i++){
int to=ac.ch[lp.P][i];
int NS=lp.S|ac.has[ac.ch[lp.P][i]];
int NID=get(to,NS);
if(!vis[NID]){
dis[NID]=dis[get(lp.P,lp.S)]+;
vis[NID]=;
from[NID]=i+;//warning!!!!
pre[NID]=get(lp.P,lp.S);
node kk;
kk.P=to;kk.S=NS;
q.push(kk);
}
}
}
}
int len;
int tot;
char ans[M];
char a[M];
bool fl;
bool cmp(char *a,char *b){//a better than b?
for(int i=;i<=len;i++){
if(a[i]<b[i]) return ;
if(a[i]>b[i]) return ;
}
}
int main(){
scanf("%d",&n);
for(int i=;i<=n;i++){
scanf("%s",s+);
int l=strlen(s+);
ac.ins(s,l,i);
}
ac.build();
bfs();
len=inf;
for(int i=;i<=ac.cnt;i++){
int id=get(i,(<<n)-);
len=min(len,dis[id]);
}
fl=false;
//cout<<" len "<<len<<endl;
for(int i=;i<=ac.cnt;i++){
int id=get(i,(<<n)-);
if(dis[id]==len){
int tmp=len;
int z=id;
while(pre[z]!=-){
//cout<<z<<endl;
a[tmp]='A'+(from[z]-);
z=pre[z];tmp--;
}
if(!fl){
fl=true;
memcpy(ans,a,sizeof a);
}
else{
if(cmp(a,ans)) memcpy(ans,a,sizeof a);
}
}
}
printf("%s",ans+);
return ;
}

2

但是还不够优美!!

为什么bfs之后还要再比较一遍字符串呢??

bfs中,第一次到达一个(1<<n)-1的点,

这个点就一定是最优解的最后一个节点!!!

因为,bfs分层图保证了最短。

for char A~Z保证了字典序最优。

直接输出即可。

代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=;
const int M=;
const int U=**((<<)-)+;
const int inf=0x3f3f3f3f;
int n;
char s[];
struct trie{
int fail[M],ch[M][];
int has[M];
int cnt;
void ins(char *s,int l,int id){
int now=;
for(int i=;i<=l;i++){
int x=s[i]-'A';
if(!ch[now][x]) ch[now][x]=++cnt;
now=ch[now][x];
}
has[now]|=(<<(id-));
}
void build(){
queue<int>q;
for(int i=;i<=;i++){
if(ch[][i]) fail[ch[][i]]=,q.push(ch[][i]);
}
while(!q.empty()){
int x=q.front();q.pop();
has[x]|=has[fail[x]];
for(int i=;i<=;i++){
if(ch[x][i]){
fail[ch[x][i]]=ch[fail[x]][i];
q.push(ch[x][i]);
}
else ch[x][i]=ch[fail[x]][i];
}
}
}
}ac;
int get(int ptr,int st){
return ptr*(<<n)+st;
}
int dis[U];
bool vis[U];
struct node{
int P,S;
};
queue<node>q;
int pre[U];
int from[U];
int len;
char ans[M];
void bfs(){
memset(dis,0x3f,sizeof dis);
int str=get(,);
dis[str]=;
vis[str]=;
pre[str]=-;//warning!!
node nn;nn.P=,nn.S=;
q.push(nn);
while(!q.empty()){
node lp=q.front();q.pop();
for(int i=;i<=;i++){
int to=ac.ch[lp.P][i];
int NS=lp.S|ac.has[ac.ch[lp.P][i]];
int NID=get(to,NS);
if(!vis[NID]){
dis[NID]=dis[get(lp.P,lp.S)]+;
vis[NID]=;
from[NID]=i+;//warning!!!!
pre[NID]=get(lp.P,lp.S);
node kk;
kk.P=to;kk.S=NS;
q.push(kk);
if(NS==(<<n)-){
int z=NID;
while(pre[z]!=-){
ans[++len]='A'+(from[z]-);
z=pre[z];
}
return;
}
}
}
}
} int main(){
scanf("%d",&n);
for(int i=;i<=n;i++){
scanf("%s",s+);
int l=strlen(s+);
ac.ins(s,l,i);
}
ac.build();
bfs();
for(int i=len;i>=;i--) printf("%c",ans[i]);
return ;
}

3

总结:

有的时候我们只关心最优答案。

但有的时候我们也关心方案。(毕竟知道方案比较实用)

方案的输出就要求高了一些。

但是肯定也是在最优答案的基础上的。

关于路径转移,凑字典序最小,经常通过松弛最优解的顺序,恰好可以保证松弛路径就是最小字典序。

本题就是一个很好的例子。

[HNOI2006]最短母串问题——AC自动机+状压+bfs环形处理的更多相关文章

  1. [HNOI2006]最短母串问题 --- AC自动机 + 隐式图搜索

    [HNOI2006]最短母串问题 题目描述: 给定n个字符串(S1,S2.....,Sn),要求找到一个最短的字符串T,使得这n个字符串(S1,S2,......,Sn)都是T的子串. 输入格式: 第 ...

  2. [bzoj1195][HNOI2006]最短母串_动态规划_状压dp

    最短母串 bzoj-1195 HNOI-2006 题目大意:给一个包含n个字符串的字符集,求一个字典序最小的字符串使得字符集中所有的串都是该串的子串. 注释:$1\le n\le 12$,$1\le ...

  3. [HNOI2006]最短母串问题 AC自动机

    题面:洛谷 题解: 如果我们对这些小串建出AC自动机,那么我们所求的大串就是要求满足遍历过所有AC自动机上的叶子节点,且经过步数最少的串.如果有多个步数相同的串,要输出字典序最小的串. 在AC自动机上 ...

  4. [BZOJ1195]:[HNOI2006]最短母串(AC自动机+BFS)

    题目传送门 题目描述 给定n个字符串(S1,S2,…,Sn),要求找到一个最短的字符串T,使得这n个字符串(S1,S2,…,Sn)都是T的子串. 输入格式 第一行是一个正整数n,表示给定的字符串的个数 ...

  5. bzoj1195 [HNOI2006]最短母串 AC 自动机+状压+bfs

    题目传送门 https://lydsy.com/JudgeOnline/problem.php?id=1195 题解 建立 AC 自动机,然后构建出 trie 图. 然后直接在 trie 图上走.但是 ...

  6. P2322 [HNOI2006]最短母串问题

    P2322 [HNOI2006]最短母串问题 AC自动机+bfs 题目要求:在AC自动机建的Trie图上找到一条最短链,包含所有带结尾标记的点 因为n<12,所以我们可以用二进制保存状态:某个带 ...

  7. BZOJ_1195_[HNOI2006]最短母串_AC自动机+BFS+分层图

    BZOJ_1195_[HNOI2006]最短母串_AC自动机+BFS+分层图 Description 给定n个字符串(S1,S2,„,Sn),要求找到一个最短的字符串T,使得这n个字符串(S1,S2, ...

  8. 【状态压缩dp】1195: [HNOI2006]最短母串

    一个清晰的思路就是状压dp:不过也有AC自动机+BFS的做法 Description 给定n个字符串(S1,S2,„,Sn),要求找到一个最短的字符串T,使得这n个字符串(S1,S2,„,Sn)都是T ...

  9. bzoj 1195: [HNOI2006]最短母串 爆搜

    1195: [HNOI2006]最短母串 Time Limit: 10 Sec  Memory Limit: 32 MBSubmit: 894  Solved: 288[Submit][Status] ...

随机推荐

  1. PHP:Iterator(迭代器)接口和生成器

    迭代器 可在内部迭代自己的外部迭代器或类的接口.详情:http://php.net/manual/zh/class.iterator.php 接口摘要 Iterator extends Travers ...

  2. npp基本设置

    经过实践,本人发现Notpad++是一个很不错的软件,无论是用于文档的读取还是开发,都很赞,那么给软件做一些基本的设置,使用的时候更得心用手就显得尤为重要了. 本文主要介绍npp的基础设置,后期会不断 ...

  3. [转]50 Tips for Working with Unity (Best Practices)

    About these tips These tips are not all applicable to every project. They are based on my experience ...

  4. 阿里与ShopRunner达成协议 联手在国内推出服务

    阿里巴巴集团与美国在线零售商 ShopRunner 达成协议,将帮助后者在中国大陆销售商品和履行订单交付产品. ShopRunner 首席战略官菲奥娜·迪亚斯(Fiona Dias)周三接受媒体采访时 ...

  5. LCA(Tarjan算法)模板

    一.查询一组的LCA Nearest Common Ancestors A rooted tree is a well-known data structure in computer science ...

  6. 王者荣耀交流协会final发布WBS+PSP

    WBS: PSP: 时间为估计,大致精确. 类型 personal software process stages 预估时间 实际花费时间 planning 计划 4h 4h estimate 4h ...

  7. c# 免费版pdf转word尝试

    链接:https://pan.baidu.com/s/1Dwuezo6YGe9CdlSyrwQyNg 密码:c81a 1.安装此程序 2.在安装文件的bin下拷贝dll: 3.代码引用 private ...

  8. SGU 181 X-Sequence(一题比较水的求模找规律)

    E - X-Sequence Time Limit:500MS     Memory Limit:4096KB     64bit IO Format:%I64d & %I64u Submit ...

  9. VC++调试基础

    一.调试基础 调试快捷键 F5:  开始调试 Shift+F5: 停止调试 F10:   调试到下一句,这里是单步跟踪 F11:   调试到下一句,跟进函数内部 Shift+F11:  从当前函数中跳 ...

  10. KeyBoard 操作 !

    键盘操作:tab/ enter/ crtl+c ,crtl+v ; import java.awt.*; import java.awt.datatransfer.StringSelection; i ...