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的)。