[CF452E]Three strings
題目
題解
演算法一
暴力做,列舉 \(A\) 的字串,在 \(B,C\) 中暴力找,時間複雜度 \(\mathcal O(n^4)\).
演算法二
同樣要在 \(A\) 中列舉字串,但是考慮在 \(L=1\) 時,對於 \(A\) 的每個字元,我們可以在 \(B,C\) 中找出相匹配的,隨著 \(L\) 變大,可能的相同的部分也只可能在之前匹配的字元之後出現,所以我們可以考慮用類似於 vector
一類的東西將位置存下來,時間複雜度 \(\mathcal O(\tt{TLE})\).
演算法三(SA)
考慮使用 \(\tt SA\)
首先,處理這種問題的關鍵是我們得將 \(A,B,C\) 首尾相接得到 \(S\),中間使用分隔符隔開,接下來我們稱其為 \(S\) 的 \(A,B,C\) 三個部分.
接下來,字串內部的匹配問題就轉化為字尾的 \(\tt LCP\) 問題,對於原來 \(A,B,C\) 三個串長度為 \(L\) 的匹配,現在即為屬於三個不同部分的字尾的 \(\texttt{LCP} \ge L\) 即可.
但是,對於不同的部分,他們的 \(\tt LCP\) 並非是單調的,比如我們有個字尾排序的陣列長這樣
對於 \(L=4\) 時,可能有這些部分的 \(\tt LCP\)
那麼 \(\tt ans[4]\) 就是各個部分的 \(\tt cnt[A] \cdot cnt[B] \cdot cnt[C]\),其中 \(\tt cnt[i]\) 表示屬於 \(i\) 部分的字尾的個數.
但是,隨著 \(L\) 變大,各個部分有可能會斷開,這讓我們非常不好處理,既然 \(L\) 變大會斷開,那麼同樣意味著,如果我們倒著處理 \(L\),那麼隨著 \(L\) 的變小,會有越來越多的部分連線在一起,這個時候我們只需要維護 cnt[i]
即可,而這個部分可以使用並查集來做,並查集合並的時候,我們也可以維護答案.
關鍵程式碼:
const int jzm=1000000007; /** @brief 全域性答案*/ int sy=0; /** @brief 對應長度的答案*/ int ans[maxn+5]; struct node{ // x 是字尾編號 int x,v; inline int operator <(const node rhs)const{ return v>rhs.v; } }v[maxn+5+5]; int fa[maxn+5]; int find(const int x){return fa[x]==x?x:fa[x]=find(fa[x]);} int cock(const int x){ return 1ll*tot[x][1]*tot[x][2]%jzm*tot[x][3]%jzm; } inline void merge(int x,int y){ // printf("merge:> x == %d, y == %d\n",x,y); x=find(x),y=find(y); if(x==y)return; sy-=cock(x);if(sy<0)sy+=jzm; sy-=cock(y);if(sy<0)sy+=jzm; fa[x]=y; rep(j,1,3)tot[y][j]+=tot[x][j]; sy+=cock(y); if(sy>=jzm)sy-=jzm; } inline void solve(){ rep(i,1,n)v[i]=node{sa[i],heit[i]}; rep(i,1,n)fa[i]=i; sort(v+1,v+n+1); // rep(i,1,n)printf("v[%d] : x == %d, v == %d\n",i,v[i].x,v[i].v); int now=1; fep(i,min_len,1){ while(now<=n && v[now].v>=i){ // printf("When i == %d, now == %d\n",i,now); int pre=sa[rk[v[now].x]-1]; merge(pre,v[now].x); ++now; } ans[i]=sy; } rep(i,1,min_len)printf("%d ",ans[i]); }
演算法四(SAM)
這個方法再等會可能就可以完善了.