日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

[XSY] 最长公共子串对(后缀自动机)

發布時間:2023/12/3 编程问答 51 豆豆
生活随笔 收集整理的這篇文章主要介紹了 [XSY] 最长公共子串对(后缀自动机) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

最長公共子串對

先放一段考場暴力代碼:

#include<iostream> #include<cstdio> #include<cstring> using namespace std; char a[2005],b[2005]; int na,nb,f[2005][2005],c[2005][2005],ans; //f[i][j]表示 s[1...i]與 t[1...j]以s[i]、t[j]結尾的最長公共子串長度 int lowbit(int x){return x&(-x); } void update(int x,int y,int v){for(int i=x;i<=na;i+=lowbit(i)){for(int j=y;j<=nb;j+=lowbit(j)){c[i][j]=max(c[i][j],v);}} } int query(int x,int y){int res=0;for(int i=x;i;i-=lowbit(i)){for(int j=y;j;j-=lowbit(j)){res=max(res,c[i][j]);}}return res; } int main(){scanf("%s%s",a+1,b+1);na=strlen(a+1);nb=strlen(b+1);for(int i=1;i<=na;i++){for(int j=1;j<=nb;j++){if(a[i]==b[j]) f[i][j]=max(f[i][j],f[i-1][j-1]+1);ans=max(ans,query(i-f[i][j],j-f[i][j])+f[i][j]);update(i,j,f[i][j]);}}printf("%d\n",ans);return 0; }

在這段代碼中,能優化的地方有兩個:

1.求最長公共子串(即求fff)可以用SAM,對s串建一個SAM,讓 t 在 s 的SAM上做匹配
(這是一個非常經典的問題,但我考試時連SAM是什么都忘了。。。)

2.我拼接兩段公共子串時,用的是二維樹狀數組,能不能轉為一維呢?

答案是可以的。題目的本質,其實就是拼接兩段公共子串。

s,t前綴的公共子串是s,t串的公共子串,s,t后綴的公共子串當然也是。

如果我們枚舉i,拼接t[1…i]與s前綴的公共子串和t[i+1…n]與s后綴的公共子串,那么就只需保證 我們所選的兩段公共子串在s串上不相交,這個用一維樹狀數組即可維護

正序t 在正序s 的SAM上做匹配,可以求s,t前綴的最長公共子串
那么,逆序t 在逆序s 的SAM上做匹配,即可以求s,t后綴的最長公共子串。

#include<iostream> #include<cstdio> #include<cstring> #include<vector> using namespace std; const int N=2e5+5; struct SAM{int tot,lst,ch[N<<1][26],len[N<<1],fa[N<<1],pos[N<<1];//fa:parent tree上的faint tax[N<<1],id[N<<1];SAM(){tot=lst=1;}void add(int c,int id){int p=lst,np=lst=++tot;len[np]=len[p]+1;pos[np]=id;for(;p&&!ch[p][c];p=fa[p])//在這里,p=fa(p)即從長到短遍歷舊串的所有后綴。ch[p][c]=np;//舊串后綴加c構成新串后綴 if(!p) fa[np]=1;else{int q=ch[p][c];if(len[q]==len[p]+1) fa[np]=q;//q中原有的子串同時也是新串的后綴 else{int nq=++tot;for(int i=0;i<26;i++) ch[nq][i]=ch[q][i];fa[nq]=fa[q];len[nq]=len[p]+1;pos[nq]=pos[q];fa[q]=fa[np]=nq;for(;p&&ch[p][c]==q;p=fa[p]) ch[p][c]=nq;//把是新串后綴但不是舊串后綴的子串放到新建節點nq上 }}}void sort(){for(int i=1;i<=tot;i++) tax[len[i]]++;for(int i=1;i<=tot;i++) tax[i]+=tax[i-1];for(int i=tot;i>=1;i--) id[tax[len[i]]--]=i;//基數排序: len為第一關鍵字,for(i=tot;i>=1;i--)時保證fa[id[i]]在id[i]后遍歷到 } }pre,suf; char s[N],t[N]; int n,m,tag[N<<1],ans; struct Qry{int length,p; }; vector<Qry> pf[N],sf[N]; int c[N]; int lowbit(int x){return x&(-x); } void update(int x,int v){for(int i=x;i<=n;i+=lowbit(i)) c[i]=max(c[i],v); } int query(int x){int res=0;for(int i=x;i;i-=lowbit(i)) res=max(res,c[i]);return res; } int main(){scanf("%s%s",s+1,t+1);n=strlen(s+1);m=strlen(t+1);for(int i=1;i<=n;i++) pre.add(s[i]-'a',i);for(int i=n;i>=1;i--) suf.add(s[i]-'a',n-i+1);pre.sort();suf.sort();for(int i=1,u=1,len=0;i<=m;i++){//正序t 在正序s 的SAM上做匹配(求s,t前綴的最長公共子串) int c=t[i]-'a';while(u&&!pre.ch[u][c]) u=pre.fa[u];if(!u){u=1;len=0;}else{len=min(len,pre.len[u])+1;//注意寫法 u=pre.ch[u][c];}if(u>1&&!tag[pre.fa[u]]) tag[pre.fa[u]]=i;//節點u的祖先中s的子串,同時也都是t的長度為i的前綴的后綴。pf[i].push_back((Qry){len,pre.pos[u]});}for(int i=pre.tot;i>=1;i--){int u=pre.id[i];if(!tag[u]) continue;if(!tag[pre.fa[u]]||tag[pre.fa[u]]>tag[u]) tag[pre.fa[u]]=tag[u];pf[tag[u]].push_back((Qry){pre.len[u],pre.pos[u]});} memset(tag,0,sizeof(tag));for(int i=m,u=1,len=0;i>=1;i--){//逆序t 在逆序s 的SAM上做匹配(求s,t后綴的最長公共子串) int c=t[i]-'a';while(u&&!suf.ch[u][c]) u=suf.fa[u];if(!u){u=1;len=0;}else{len=min(len,suf.len[u])+1;u=suf.ch[u][c];}if(u>1&&!tag[suf.fa[u]]) tag[suf.fa[u]]=i;sf[i].push_back((Qry){len,suf.pos[u]});} for(int i=suf.tot;i>=1;i--){int u=suf.id[i];if(!tag[u]) continue;if(!tag[suf.fa[u]]||tag[suf.fa[u]]<tag[u]) tag[suf.fa[u]]=tag[u];sf[tag[u]].push_back((Qry){suf.len[u],suf.pos[u]});}ans=0;for(int i=m;i>=1;i--){for(int j=0;j<pf[i].size();j++){Qry q=pf[i][j];ans=max(ans,q.length+query(n-q.p));}for(int j=0;j<sf[i].size();j++){Qry q=sf[i][j];if(q.p) update(q.p,q.length);}}printf("%d\n",ans);return 0; }

總結

以上是生活随笔為你收集整理的[XSY] 最长公共子串对(后缀自动机)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。