【洛谷7114】[NOIP2020] 字符串匹配(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;
}
posted @ 2020-12-10 19:07  TheLostWeak  阅读(63)  评论(0编辑  收藏