最短母串

Ac_automaton的与状压的结合。

看题解说是Ac_automaton上的dp,但是实际上没有十分明显的转移过程,仅仅使用状压的方式记录某个串是否被选择过了(当然有建完Ac_automation然后跑纯状压dp的解法)。

首先建立Ac_automaton(Trie图),额外维护一个sta数组,该数组为一个二进制表示数组,‘1’表示该串出现过,‘0’表示该串为出现过。这个信息需要在insert与generate(build)过程中统计,然而有趣的是,网上的大部分题解仅有由fail更新当前节点的处理,而没有从父亲转移过来的信息,但依旧可以A掉。

至于维护原理,由于fail指针指向最长后缀,试想,某串的后缀都出现了,他可能没出现吗?接着想,某串的父串(包含它的串,与子串相对)都出现了,那它可能没出现吗。

接着是如何求解ans。首先思考,我们如何满足最短和字典序问题。

bfs时,由于层层外推的特性,使得当我们遍历到一个可行状态,那么他就是最短的,因为我们此时说的“层”就是长度。

字典序一般只要从A->Z枚举就可以保证(这个应该比上一个好想)。

剩下的就是记录路径的问题了,我们可以在bfs结构体里维护一个id,表示当前状态推到下一个状态后,下一个状态父亲状态编号(好像说麻烦了),我们利用前继的方法来记录该状态是由那个状态而来,找到目标状态以后,直接递归输出即可。

其实挺不好想得。

具体实现细节可以参考代码,指针打完以后有点超时(后来发现我莫名其妙有一个map的logn),改成数组了,记录方便一点。

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<vector>
#include<queue>
#include<set>
#include<map>
using namespace std;
struct node{
    int p,state,id;
};
struct Pre{
    int nex,num;
}pre[60000000];
int trie[6000][26];
int n,tot,root,cnt,lis[6000];
int sta[6000],fail[6000];
char s[60];
bool v[6000][1<<15];
void mclear(){
    tot=root=1;
}
void insert(int x){
    int now=root,l=strlen(s+1);
    for(int i=1;i<=l;i++){
        if(!trie[now][s[i]-‘A‘]) trie[now][s[i]-‘A‘]=++tot;
        now=trie[now][s[i]-‘A‘];
    }sta[now]|=(1<<(x-1));
}
void generate(){
    queue<int>q;
    for(int i=0;i<26;i++)
        if(trie[root][i]){
            fail[trie[root][i]]=root;
            q.push(trie[root][i]);
        }else trie[root][i]=root;
    while(!q.empty()){
        int now=q.front();
        q.pop();
        for(int i=0;i<26;i++)
                if(trie[now][i]){
                    fail[trie[now][i]]=trie[fail[now]][i];
                    q.push(trie[now][i]);
                    sta[trie[now][i]]|=sta[now];
                }else trie[now][i]=trie[fail[now]][i];
    }
    for(int i=1;i<=tot;i++)
        sta[i]|=sta[fail[i]];
}
void print(int x){
    if(!x) return ;
    print(pre[x].nex);
    printf("%c",pre[x].num+‘A‘);
}
void bfs(){
    queue<node>q;int head=0,tail=1;
    q.push((node){1,0});
    v[1][0]=1;
    int ends=(1<<n)-1;
    while(!q.empty()){
        node x=q.front();
        q.pop();
        int now=x.p,stt=x.state,id=x.id;
        if(ends==stt){
                print(id);
                return ;
        }
        for(int i=0;i<26;i++){
            int y=trie[now][i];
            if(v[y][sta[y]|stt]) continue;
            pre[++cnt].nex=id;
            pre[cnt].num=i;
            v[y][sta[y]|stt]=1;
            q.push((node){y,sta[y]|stt,cnt});
        }
    }
}
int main(){
    scanf("%d",&n);
    mclear();
    for(int i=1;i<=n;i++){
        scanf("%s",s+1);
        insert(i);
    }
    generate();
    bfs();
    return 0;
}

交了小oj,结果这天bzoj好像维护,没试代码,缩进有点丑,凑合看看吧,主要明白为什么状压和bfs就好了,看明白后可以上网找一下状压dp的题解(虽然这个题主要是为了学Ac_automaton的)。

原文地址:https://www.cnblogs.com/Yu-shi/p/11072109.html

时间: 2024-11-05 12:21:24

最短母串的相关文章

BZOJ 1195: [HNOI2006]最短母串

1195: [HNOI2006]最短母串 Time Limit: 10 Sec  Memory Limit: 32 MBSubmit: 1346  Solved: 450[Submit][Status][Discuss] Description 给定n个字符串(S1,S2,„,Sn),要求找到一个最短的字符串T,使得这n个字符串(S1,S2,„,Sn)都是T的子串. Input 第一行是一个正整数n(n<=12),表示给定的字符串的个数.以下的n行,每行有一个全由大写字母组成的字符串.每个字符串

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

1195: [HNOI2006]最短母串 Time Limit: 10 Sec  Memory Limit: 32 MBSubmit: 894  Solved: 288[Submit][Status][Discuss] Description 给定n个字符串(S1,S2,„,Sn),要求找到一个最短的字符串T,使得这n个字符串(S1,S2,„,Sn)都是T的子串. Input 第一行是一个正整数n(n<=12),表示给定的字符串的个数.以下的n行,每行有一个全由大写字母组成的字符串.每个字符串的

[BZOJ1195]最短母串

1195: [HNOI2006]最短母串 Time Limit: 10 Sec  Memory Limit: 32 MB Description 给定n个字符串(S1,S2,?,Sn),要求找到一个最短的字符串T,使得这n个字符串(S1,S2,?,Sn)都是T的子串. Input 第一行是一个正整数n(n<=12),表示给定的字符串的个数.以下的n行,每行有一个全由大写字母组成的字符串.每个字符串的长度不超过50. Output 只有一行,为找到的最短的字符串T.在保证最短的前提下,如果有多个字

2782: [HNOI2006]最短母串

2782: [HNOI2006]最短母串 Time Limit: 1 Sec  Memory Limit: 128 MBSubmit: 3  Solved: 2[Submit][Status][Web Board] Description 给定n个字符串(S1,S2,„,Sn),要求找到一个最短的字符串T,使得这n个字符串(S1,S2,„,Sn)都是T的子串. Input 第一行是一个正整数n(n<=12),表示给定的字符串的个数.以下的n行,每行有一个全由大写字母组成的字符串.每个字符串的长度

Bzoj1195 [HNOI2006]最短母串 [AC自动机]

Time Limit: 10 Sec  Memory Limit: 32 MBSubmit: 1304  Solved: 439 Description 给定n个字符串(S1,S2,„,Sn),要求找到一个最短的字符串T,使得这n个字符串(S1,S2,„,Sn)都是T的子串. Input 第一行是一个正整数n(n<=12),表示给定的字符串的个数.以下的n行,每行有一个全由大写字母组成的字符串.每个字符串的长度不超过50. Output 只有一行,为找到的最短的字符串T.在保证最短的前提下,如果

【题解】最短母串

题目大意 ??有\(n\)个字符串\(s_1,s_2,\dots,s_n\),求一个最短的字符串\(S\),使这\(n\)个字符串都是\(S\)的子串. ? 题解 ??我们先对这\(n\)个字符串建AC自动机,这里我们对于Trie上的结点\(i\),定义一个状态\(state_i\),表示第\(i\)个结点对应的字符串,包含的题目中给出的哪些字符串. ??我们可以从Trie上的根节点,按照字典序BFS,我们用\(f\)表示当前经过的所有结点的包含的所有字符串,显然,当\(f\)包含所有字符串时,

python 字符串函数find功能拓展——查找母串内所有子串的位置

前言 我们知道,字符串内置了很多功能的处理函数,其中,find.index函数都可以接受一个参数意义是作为目标子串,而返回母串中从左到右遍历时子串第一次出现的索引值(每一次调用都是从头开始,没有记忆),如果查询不到返回-1. 如下面的例子: 如果,子串不在母串中出现,则find函数返回-1,而index方法返回ValueError错误,这也是两者的区别,接上例: 深入 rindex rfind函数:功能类似,把母串从右向左遍历,找到子串第一次出现的位置,也没有记忆性. 后续 我编写了一个函数,实

dp-LCS(递归输出最短合串)

Problem Description The company "21st Century Fruits" has specialized in creating new sorts of fruits by transferring genes from one fruit into the genome of another one. Most times this method doesn't work, but sometimes, in very rare cases, a

2017&quot;百度之星&quot;程序设计大赛 - 资格赛 寻找母串

Problem Description 对于一个串S,当它同时满足如下条件时,它就是一个01偏串: 1.只由0和1两种符组成: 2.在S的每一个前缀中,0的个数不超过1的个数: 3.S中0的个数和1的个数相等. 现在给定01偏串S,请计算一下S在所有长度为n的01偏串中作为子串出现的次数的总和. 由于结果比较大,结果对1e9+7取余后输出. 样例解释: 在第二个样例中,长度为4的偏串共两个1010,1100.10在1010中出现了两次,在1100中出现了1次.所以答案是3. Input 第一行给