[HEOI2015]最短不公共子串 序列自動機—— [FJOI2016]所有公共子序列問題
阿新 • • 發佈:2018-12-12
四合一的題。
簡單粗暴的方法:
子串匹配——SAM
子序列匹配——序列自動機
關於序列自動機:序列自動機—— [FJOI2016]所有公共子序列問題
(其實這個玩意沒有什麼,n+1個點,每個點的字符集的每條出邊連向其後的第一個字元,這樣保證儘可能用靠前的,後面的能湊出的子序列就能更多,1號點是rt)
類似於SAM,序列自動機的路徑條數就是子序列個數。
這樣的話,對AB兩個串各建造一個SAM和序列自動機
四問就分別在兩個機子上bfs跑
如果A有的出邊,而B沒有,那麼返回深度作為答案。
否則A,B都有,入隊。
正確性:bfs按深度,會把深度為d的所有公共的子串/子序列搜完後再搜下一個。第一個合法的就是最短的。
複雜度:記憶化一下,因為到了同樣一個數對(Ax,By)時候,後面的路徑都是固定的了。之前沒有找到過,後面也不會找到。直接continue
這樣,複雜度就是狀態數O(n^2)
就是考察對自動機"路徑與子串一一對應"的理解。
#include<bits/stdc++.h> #define il inline #define reg register int #define numb (ch^'0') using namespace std; typedef long long ll; il void rd(int &x){ char ch;boolfl=false; while(!isdigit(ch=getchar()))(ch=='-')&&(fl=true); for(x=numb;isdigit(ch=getchar());x=x*10+numb); (fl==true)&&(x=-x); } namespace Miracle{ const int N=2002; char a[N],b[N]; int l1,l2; struct SAM{ int nd,cnt,fa[2*N],ch[2*N][26]; int len[2*N]; SAM(){ nd=cnt=1; } void ins(int c,int pos){ //cout<<c<<" "<<pos<<" "<<cnt<<endl; int p=nd;nd=++cnt; len[nd]=pos; for(;p&&ch[p][c]==0;p=fa[p]) ch[p][c]=nd; if(p==0) {fa[nd]=1;return;} int q=ch[p][c]; if(len[q]==len[p]+1){fa[nd]=q;return;} len[++cnt]=len[p]+1; for(reg i=0;i<=25;++i) ch[cnt][i]=ch[q][i]; fa[cnt]=fa[q];fa[q]=cnt;fa[nd]=cnt; for(;p&&ch[p][c]==q;p=fa[p]) ch[p][c]=cnt; } }SA,SB; struct XU{ int cnt,ch[N][26]; int las[26]; void build(char *s,int len){ for(reg i=len;i>=0;--i){ for(reg j=0;j<=25;++j){ if(las[j]) { ch[i+1][j]=las[j]; //cout<<" to "<<i+1<<" "<<j<<" "<<las[j]<<endl; } } if(i)las[s[i]-'a']=i+1; } } }XA,XB; struct po{ int x,y,d; po(){} po(int xx,int yy,int dd){ x=xx,y=yy,d=dd; } }; queue<po>q; int vis[2*N][2*N]; int bfs1(){ while(!q.empty()) q.pop(); q.push(po(1,1,0)); vis[1][1]=1; while(!q.empty()){ po now=q.front();q.pop(); for(reg i=0;i<=25;++i){ if(vis[SA.ch[now.x][i]][SB.ch[now.y][i]]==1) continue; vis[SA.ch[now.x][i]][SB.ch[now.y][i]]=1; if(SA.ch[now.x][i]&&!SB.ch[now.y][i]) return now.d+1; if(SA.ch[now.x][i]) { q.push(po(SA.ch[now.x][i],SB.ch[now.y][i],now.d+1)); } } } return -1; } int bfs2(){ while(!q.empty()) q.pop(); q.push(po(1,1,0)); vis[1][1]=2; while(!q.empty()){ po now=q.front();q.pop(); for(reg i=0;i<=25;++i){ if(vis[SA.ch[now.x][i]][XB.ch[now.y][i]]==2) continue; vis[SA.ch[now.x][i]][XB.ch[now.y][i]]=2; if(SA.ch[now.x][i]&&!XB.ch[now.y][i]) return now.d+1; if(SA.ch[now.x][i]) { q.push(po(SA.ch[now.x][i],XB.ch[now.y][i],now.d+1)); } } } return -1; } int bfs3(){ while(!q.empty()) q.pop(); q.push(po(1,1,0)); vis[1][1]=3; while(!q.empty()){ po now=q.front();q.pop(); for(reg i=0;i<=25;++i){ if(vis[XA.ch[now.x][i]][SB.ch[now.y][i]]==3) continue; vis[XA.ch[now.x][i]][SB.ch[now.y][i]]=3; if(XA.ch[now.x][i]&&!SB.ch[now.y][i]) return now.d+1; if(XA.ch[now.x][i]) { q.push(po(XA.ch[now.x][i],SB.ch[now.y][i],now.d+1)); } } } return -1; } int bfs4(){ while(!q.empty()) q.pop(); q.push(po(1,1,0)); vis[1][1]=4; while(!q.empty()){ po now=q.front();q.pop(); //cout<<now.x<<" || "<<now.y<<" dd "<<now.d<<endl; for(reg i=0;i<=25;++i){ if(vis[XA.ch[now.x][i]][XB.ch[now.y][i]]==4) continue; vis[XA.ch[now.x][i]][XB.ch[now.y][i]]=4; if(XA.ch[now.x][i]&&!XB.ch[now.y][i]) return now.d+1; if(XA.ch[now.x][i]) { //cout<<" goto "<<XA.ch[now.x][i]<<" "<< q.push(po(XA.ch[now.x][i],XB.ch[now.y][i],now.d+1)); } } } return -1; } int main(){ scanf("%s",a+1); scanf("%s",b+1); l1=strlen(a+1);l2=strlen(b+1); for(reg i=1;i<=l1;++i) SA.ins(a[i]-'a',i); for(reg i=1;i<=l2;++i) SB.ins(b[i]-'a',i); //cout<<SA.cnt<<" "<<SB.cnt<<endl; XA.build(a,l1);XB.build(b,l2); printf("%d\n%d\n%d\n%d",bfs1(),bfs2(),bfs3(),bfs4()); return 0; } } signed main(){ Miracle::main(); return 0; } /* Author: *Miracle* Date: 2018/12/12 9:10:40 */
考慮到,我們要最小化一個串的長度,使得這個串在B的機子上跑完回到NULL節點。
所以,也可以DP做。f[i][j]表示,考慮A中前i個位置,匹配到B的機子上的j位置,最小長度。直接在所有機子位置後嘗試匹配i+1位,取min即可完成轉移。
答案就是f[lenA][NULL]