【洛谷7114】[NOIP2020] 字符串匹配(Z函数)

点此看题面

  • 给定一个长度为(n)的字符串(s)
  • 问有多少组种非空字符串(A,B,C),满足(s=(AB)^kC),且(A)中出现次数为奇数的字符个数小于等于(C)中的个数。
  • 数据组数(le5,nle2^{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)

那么可行的循环次数实际上就是(lfloorfrac{min{Z(i+1),n-i-1}}i floor+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
",ans);
	}return 0;
}
原文地址:https://www.cnblogs.com/chenxiaoran666/p/Luogu7114.html