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