【洛谷7114】字串匹配(Z函式)
阿新 • • 發佈:2020-12-10
- 給定一個長度為\(n\)的字串\(s\)。
- 問有多少組種非空字串\(A,B,C\),滿足\(s=(AB)^kC\),且\(A\)中出現次數為奇數的字元個數小於等於\(C\)中的個數。
- 資料組數\(\le5,n\le2^{20}\)
暴力做法
考慮我們去列舉迴圈節\(AB\),列舉迴圈次數\(k\),暴力雜湊判斷是否可行。
這樣的複雜度應該是\(O(Tn(\ln n+26))\)的,剛好被卡掉。
一個簡單的小優化
首先我們考慮一個小優化,把複雜度中的\(26\)去掉。
設\(x=k\%2\),顯然偶數個迴圈節中所有字元出現次數必然都是偶數,它們並不會對奇偶性造成任何影響。
然後考慮當\(x=0\)
而\(x=1\)的時候,\(C\)中奇數字符個數就是當前字尾奇數字符個數,由於這個值每次只會修改\(1\),我們可以動態維護它。
這樣一來,我們的核心問題就是如何去掉\(\ln n\)了。
\(Z\)函式
可以詳見這篇部落格:擴充套件 KMP(Z 函式)學習筆記。
眾所周知,如果\(i\)是長度為\(len\)的子串\([1,len]\)的迴圈節,充要條件就是子串\([1,len-i]\)與\([i+1,len]\)完全相同。
發現這等價於第\(i+1\)個字尾與原串的\(LCP\)(即\(Z(i+1)\))大於等於\(len-i\)
那麼可行的迴圈次數實際上就是\(\lfloor\frac{\min\{Z(i+1),n-i-1\}}i\rfloor+1\)(注意,要取\(\min\)是因為\(C\)不能為空),其中一半\(k\)為奇數,一半\(k\)為偶數。
這樣一來就能\(O(1)\)計算了。
程式碼:\(O(Tn)\)
#include<bits/stdc++.h> #define Tp template<typename Ty> #define Ts template<typename Ty,typename... Ar> #define Reg register #define RI Reg int #define Con const #define CI Con int& #define I inline #define W while #define N 1048576 using namespace std; int n,a[30],b[30],g[30];char s[N+5]; int Z[N+5];I void GetZ()//預處理Z函式 { RI i,id,Mx=0;for(Z[1]=n,i=2;i<=n;i+Z[i]-1>Mx&&(Mx=i+Z[id=i]-1),++i) {Z[i]=i<=Mx?min(Z[i-id+1],Mx-i+1):0;W(i+Z[i]<=n&&s[i+Z[i]]==s[1+Z[i]]) ++Z[i];} } int main() { RI Tt,i,p,q,o,u,v,k;long long ans;scanf("%d",&Tt);W(Tt--) { for(scanf("%s",s+1),n=strlen(s+1),GetZ(),i=1;i<=26;++i) a[i]=b[i]=g[i]=0;//清空 for(p=q=o=u=v=0,i=1;i<=n;++i) (b[s[i]&31]^=1)?(++o,++q):(--o,--q);//統計整個串奇數字符個數 for(ans=0,i=1;i^n;++i) (b[s[i]&31]^=1)?(u+=g[++q]):(u-=g[q--]),//更新當前字尾中奇數字符個數,同時維護基數情況下合法A的個數 i>1&&(k=min(Z[i+1],n-i-1)/i+1,ans+=1LL*(k+1>>1)*u+1LL*(k>>1)*v),//計算答案 ++g[(a[s[i]&31]^=1)?++p:--p],p<=q&&++u,p<=o&&++v;//加入一個可能的A,更新兩種情況合法A的個數 printf("%lld\n",ans); }return 0; }