最短母串

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;
}
View Code

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

小舟从此逝,沧海寄余生。

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