1. 程式人生 > 實用技巧 >【洛谷7114】字串匹配(Z函式)

【洛谷7114】字串匹配(Z函式)

點此看題面

  • 給定一個長度為\(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\)

的時候,\(C\)中奇數字符個數其實就是整個原串中奇數字符個數。

\(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;
}