1. 程式人生 > 實用技巧 >NOIP2020 T2字串匹配

NOIP2020 T2字串匹配

一個字串,把它寫成\((AB)^iC\)的形式,並且要求\(f(A)\le f(C)\),其中\(f(S)\)表示字串\(S\)中出現了奇數次的字元。統計合法四元組\((A,B,C,i)\)\(A,B,C\)不為空串,\(i>0\))的個數。

\(n\le 2^{20}\)


這就是NOIP題嗎,愛了愛了。

當場寫了個\(O(n\ln n+26n)\)的做法,用了雙雜湊常數有點大。而且我雜湊的時候是直接用迴圈串的性質判的,而不是對於每個迴圈節分別判,這導致我甚至沒有意識到可以break

聽說有人直接unsigned long long雜湊過了?

可以發現性質:如果\((AB)^iC\)合法,那麼\((AB)^{i-2}C\)

一定合法。

列舉\(AB\)長度\(len\),分別考慮\(AB(AB)^{2i}C\)\(ABAB(AB)^{2i}C\)的情況。以前者為例。

一種做法:還是雙雜湊,然後二分出最大的\(i\)。這一部分的時間是\(\sum \lg\frac{n}{i}=n\lg n-\sum \lg i\),估算一下\(\int \lg i=n\lg n-n\),然後發現時間複雜度是\(O(n)\)

另一種做法:寫個exkmp,問\(LCP(s_{len+1\dots n},s_{1\dots n})\),就可以得到最大的\(i\)

搞完這些這題基本做完了。

還需要查一下\(f(C)\)固定時合法的\(f(A)\)

有多少個。由於常數小,可以直接\(O(26n)\)搞過去;也可以\(O(n\lg 26)\)。或者還可以發現:你想要查的\(f(C)\)的變化量是\(O(1)\)的,於是維護個桶和一個值,當\(len\)增大時對這個值改一下即可,那時間就是\(O(n)\)


using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N (1<<20|5)
#define ll long long
int n;
char s[N];
int ex[N];
void init(){
	ex[1]=n;
	int p=0,mx=0;
	for (int i=2;i<n;++i){
		ex[i]=0;
		if (i<=mx)
			ex[i]=min(mx-i+1,ex[i-p+1]);
		while (s[1+ex[i]]==s[i+ex[i]] && i+ex[i]<n)
			++ex[i];
		if (i+ex[i]-1>mx)
			mx=i+ex[i]-1,p=i;
	}
	ex[n+1]=0;
}
int buc[27],cnt;
int f[N];
int g[27];
int main(){
	freopen("in.txt","r",stdin);
//	freopen("out.txt","w",stdout);
	int T;
	scanf("%d",&T);
	while (T--){
		scanf("%s",s+1);
		n=strlen(s+1);
		init();
		cnt=0,memset(buc,0,sizeof buc);
		f[n+1]=0;
		for (int i=n;i>=1;--i){
			cnt-=(buc[s[i]-'a']&1);
			cnt+=(++buc[s[i]-'a']&1);
			f[i]=cnt;
		}
//		for (int i=1;i<=n;++i)	
//			printf("%d ",f[i]);
//		printf("\n");
		memset(g,0,sizeof g);
		cnt=0,memset(buc,0,sizeof buc);
		ll ans=0;
		for (int i=1;i<n;++i){
			ll tmp=ans;
			ans+=(ll)g[f[i+1]]*(ex[i+1]/(2*i)+1);
			if (i+i+1<=n && ex[i+1]>=i)
				ans+=(ll)g[f[i+i+1]]*(ex[i+i+1]/(2*i)+1);
//			printf("%lld\n",ans-tmp);
			cnt-=(buc[s[i]-'a']&1);
			cnt+=(++buc[s[i]-'a']&1);
			for (int j=cnt;j<=26;++j)
				g[j]++;
		}
		printf("%lld\n",ans);
	}
	return 0;
}