擴充套件 KMP(Z 函式)
阿新 • • 發佈:2020-12-22
能處理的問題形式
線性求:\(\operatorname {Lcp}([i:],[:n]),i\in [1,n]\)。
演算法流程
回想一下 KMP 中我們記錄的是什麼?
\(nxt_i\) 表示 \([:i]\) 這個串中最長字首字尾匹配長度。
而現在丟給我們的問題是:求 \([i:]\) 和 \([:n]\) 的最長字首匹配,我們用 \(z_i\) 來表示這個值。
對於當前知道的右端點最靠右的匹配串 \([l:r]\)(與 \([1:r-l+1]\) 匹配),若有 \(i\le r\),那麼 \([i:r]\) 與 \([i-l+1:r-l+1]\) 匹配。
我們如何利用當前已知的東西求出匹配長度?
\([i-l+1:r-l+1]\) 與 \([:n]\) 嘗試匹配,可以看做是 \([i-l+1:]\) 與 \([:n]\) 嘗試匹配後,其大小與 \(r-i+1\) 取一個 \(\min\) 值。
而這個 \([i-l+1:]\) 與 \([:n]\) 的匹配,則可以看做是相同型別的問題,就是 \(z_{i-l+1}\)。
那麼這個轉移可以看做是沒有問題了,你已經完全理解了為什麼這麼轉移是對的,剩下部分可能還有一些往後擴充套件的,直接暴力掃過去。
複雜度分析(也許算不上嚴謹的分析)
接下來就是考慮複雜度為什麼是對的?
我們維護的 \(r\),實際上是 \([i:]\) 的最大可轉移字首的右端點,我們每次暴力擴充套件的時候,實際上就會將 \(r\)
那麼這裡的 \(l,r\) 就是雙指標,這樣的存在。
雖然說得比較模糊,但是確實就是 \(\operatorname O(n)\) 的。
模板程式碼
#include <stdio.h> #include <string> #include <string.h> #include <iostream> #define LL long long using namespace std; const int N=2e7+3; inline int min(int x,int y){return x<y?x:y;} inline LL min(LL x,LL y){return x<y?x:y;} char a[N]; char b[N]; int n,m; LL z[N]; LL ans; inline void init() { LL l,r; l=r=0; z[1]=m; ans=m+1; for(LL i=2;i<=m;i++) { if(i<=r)z[i]=min(z[i-l+1],r-i+1); for(;z[i]+i<=m&&b[z[i]+1]==b[z[i]+i];z[i]++); if(i+z[i]-1>r)l=i,r=i+z[i]-1; ans^=(z[i]+1)*i; } printf("%lld\n",ans); return; } LL p[N]; inline void work() { LL l,r; l=r=ans=0; for(LL i=1;i<=n;i++) { if(i<=r)p[i]=min(z[i-l+1],r-i+1); for(;p[i]+1<=m&&p[i]+i<=n&&b[p[i]+1]==a[p[i]+i];p[i]++); if(i+p[i]-1>r)l=i,r=i+p[i]-1; ans^=(p[i]+1)*i; } printf("%lld\n",ans); return; } int main() { // freopen("P5410_2.in","r",stdin); int i,j; cin>>(a+1)>>(b+1); n=strlen(a+1);m=strlen(b+1); a[n+1]=5; b[m+1]=10; init(); work(); return 0; }