P7114 [NOIP2020] 字符串匹配

题意

著名题目
链接

题解

参考博客
首先一个思路是 枚举循环节长度
要变成 \((AB)^kC\) 的形式 先枚举循环节\(AC\)长度\(i\)
设循环结长度为 \(i\) 显然 \(i\)\(2\)\(n-1\) 都是合法的
因为循环节非空且C非空
接下来我们想,能循环多少次呢?
这里引入扩展kmp函数\(Z[i]\) 表示从整个字符串从\(i\)号字符开始的后缀 和 整个串的 最长公共前缀
不妨自己画个图 \(t = Z[i+1]/i+1\) 就是最多能循环的次数(额外加上上循环一次)

然后循环这么多次 到底有多少种合法的方案呢?
在这里 我们设\(pre sub all\) 表示前缀、后缀、整个串里面出现奇数次的字符的个数
在枚举i的过程中 我们可以很轻松的更新这三个值

对于循环节\(AB\) 我们分类讨论他出现了奇数次还是偶数次
如果出现了偶数次 那么对整个序列的影响可以忽视
也就是说 C中出现奇数次字符的个数 等于 整个序列出现奇数次字符的个数
也就是说 我们的序列A 需要奇数字符个数 小于等于 整个数列的奇数字符个数
对于这个数量的要求 可以用树状数组 维护\(F(x)\)表示\(0-i\)所有前缀出现奇数次字符大于x的前缀个数
对于这个函数的求法 我们一会再做讨论
现在得出的结论是 这一部分对答案的贡献为

\[F(all)*(t/2) \]

接下来 如果出现了奇数次
思考后得出这一部分的贡献

\[F(suf)*(t-t/2) \]

最后 我们想想F(x)如何动态维护
每增加一个字符\(s[i]\)
我们把树状数组pre的位置+1
查询的时候可以求出在i之前的且出现奇数次字符小于等于某个数x的方案数
于是这题愉快的解决了

#include<bits/stdc++.h>
#define ll long long
#define inf 0x7fffffff
using namespace std;
#define maxn 1048576
int z[maxn];
char s[maxn];
int n;
int suf=0,all=0,pre=0;
int after[60],before[60]; 
void Z()//exkmp 
{
	z[0]=n;
	int now=0;
	while(now+1<n&&s[now]==s[now+1])now++;
	z[1]=now;
	int p0=1;
	for(int i=2;i<n;i++)
	{
		if(i+z[i-p0]<p0+z[p0])//够用 
		{
			z[i]=z[i-p0];
		}else
		{
			now=p0+z[p0]-i;
			now=max(now,0);
			while(now<n&&now+i<n&&s[now]==s[now+i])now++;
			z[i]=now;
			p0=i;
		}
	}
}
int c[maxn];
#define lowbit(x) (x&-x)
void Add(int x)
{
	while(x<maxn)
	{
		c[x]++;
		x+=lowbit(x);
	}
}
int Query(int x)
{
	int res=0;
	while(x)
	{
		res+=c[x];
		x-=lowbit(x);
	}
	return res;
}
signed main()
{
	int T;
	scanf("%d",&T);
	while(T--)
	{
		memset(z,0,sizeof(z));
		memset(c,0,sizeof(c));
		for(int i=0;i<=59;i++)after[i]=0,before[i]=0;
		scanf("%s",s);
		n=strlen(s);
		Z();
		all=pre=suf=0;
		for(int i=0;i<n;i++)
		{
			after[s[i]-'a']++;
			if(i+z[i]==n)z[i]--;
		}
		for(int i=0;i<26;i++)
		{
			if(after[i]&1)all++;
		}
		ll ans=0;
		suf=all;
		for(int i=0;i<n;i++)//枚举循环节长度i 
		{
			if(after[s[i]-'a']&1)suf--;
			else suf++; 
			after[s[i]-'a']--;
			if(before[s[i]-'a']&1)pre--;
			else pre++;
			before[s[i]-'a']++;
			if(i!=0&&i!=n-1)
			{
				int t=z[i+1]/(i+1)+1;
				ans+=1ll*Query(all+1)*(t/2)+1ll*Query(suf+1)*(t-t/2);
				//奇数偶数的答案要分开计算 
				//对于奇数次数 前面的奇数要比suf多 
				//对于偶数次数 前面的奇数要比总数多 
			}
			Add(pre+1);
		}
		printf("%lld\n",ans);
	}
	
	
	return 0;
}
/*
f[i][j]表示i到j出现奇数次的字符数量 
维护suf 表示后缀奇数次字符数量
    pre 表示前缀奇数次字符数量 
    all 表示总的奇数次字符数量 
树状数组 用来查询前缀中奇数次字符小于等于一个数p的有多少种 
Z[i]表示从i开始的后缀和整个数组的最大公共前缀 

*/

posted @ 2021-09-25 21:58  lzylzy/kk  阅读(81)  评论(0编辑  收藏  举报
浏览器标题切换
浏览器标题切换end