[NOIP2020] 字符串匹配
[NOIP2020] 字符串匹配
这真的是个蓝天吗?为什么它的前缀知识是个紫题....
算了,不管那么多了....
首先要有一个意识就是看到循环的时候,可以多向KMP的方向去靠近...(别问我为什么会知道..)
首先任何题都不是一下子想到某个算法,然后根据算法进行变形,靠近当前这个题。而应该是反过来的,我们先观察当前题有什么性质,根据它的性质或是我们需要解决的问题来确定算法之后解决。
首先看这个题\(F(s)\)的定义就很奇怪,奇数次的字符的数量。首先肯定是要将\((AB)^i\)和\(C\)是分开的。考虑某种情况下,我们找到了一个划分方式\((AB)^kC\),观察他有什么性质,容易发现k的奇偶性相同时,\(F(C)\)的值是一样的。因为当k每次增加2时,C所在的集合就会减少两个一样的区间。显然这两个一样的区间是对\(F(C)\)是不构成影响的。
那我们接下来的思路就有了,枚举循环节\(AB\),然后找出它最大的循环节。之后考虑\(F(C)\)即可。
循环节最大次数可以使用扩展KMP的z函数解决。F(C)可以预处理后缀的奇数次字符的数量解决。
点击查看代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=1100000;
int T,nex[N],n,f[N],cnt[30],c[30];//nex[i]表示[i-n]的最长公共前缀的长度。
char s[N];
inline void get_next()
{
nex[1]=n;
int p0=2,p=1;
while(p+1<=n&&s[p]==s[p+1]) ++p;
nex[2]=p-1;
for(int i=3;i<=n;++i)
{
if(nex[i-p0+1]+i-1<p) nex[i]=nex[i-p0+1];
else
{
int now=p-i+1;
now=max(now,0);
while(now+i<=n&&s[now+1]==s[now+i]) ++now;
p=i+now-1;p0=i;
nex[i]=now;
}
}
}
inline void get_f()
{
memset(cnt,0,sizeof(cnt));
int ans=0;
for(int i=n;i>=1;--i)
{
int id=s[i]-'a';
cnt[id]++;
if(cnt[id]%2==1) ++ans;
else ans--;
f[i]=ans;
}
}
inline void add(int x,int v)
{
x++;
for(;x<=26;x+=(x&-x)) c[x]+=v;
}
inline int ask(int x)
{
x++;
int ans=0;
for(;x;x-=(x&-x)) ans+=c[x];
return ans;
}
int main()
{
scanf("%d",&T);
while(T--)
{
scanf("%s",s+1);
n=strlen(s+1);
get_next();get_f();
memset(cnt,0,sizeof(cnt));
memset(c,0,sizeof(c));
int t=1;ll ans=0;
cnt[s[1]-'a']++;
add(t,1);
for(int i=2;i<n;++i)//枚举每一个循环节
{
int k=nex[i+1]/i+1;//计算最大的循环节的次数。
if(i*k==n) --k;
if(k==1) ans+=ask(f[i+1]);
else
{
int j=k/2+k%2,l=k-j;//奇偶的数量
if(k%2==1) ans+=ask(f[i*k+1])*j+ask(f[i*(k-1)+1])*l;
else ans+=ask(f[i*k+1])*l+ask(f[i*(k-1)+1])*j;
}
int id=s[i]-'a';
cnt[id]++;
if(cnt[id]%2==1) t++;
else t--;
add(t,1);
// cout<<i<<' '<<ans<<endl;
}
printf("%lld\n",ans);
}
return 0;
}