[NOIP2020] 字符串匹配 题解
[NOIP2020] 字符串匹配 题解
知识点
KMP,扩展 KMP(Z 函数)。
分析
首先我们来考虑一个做法:枚举 \(AB\) 串的长度,也就是枚举前缀是 \(AB\) 串,然后再向后跳,找到满足条件的 \({(AB)}^iC\),这里用前后缀和的方式处理出各个前后缀出现奇数次的字符的数量,然后再开一个数组 \(pre_{i,j}\),分别表示 \(1 \sim i\) 的前缀中有几个出现奇数次字符的数量 \(\le j\),然后在满足条件的 \({(AB)}^iC\) 部分进行累加即可。
时间复杂度 \(O(\sum|S|(\log_2{|S|}+C))\),\(C\) 为字符总数。时间复杂度基于调和级数,常数较大,需要一系列卡常。
我们尝试把大常数 \(\log_2{|S|}\) 从复杂度里去掉,也就是优化求 \((AB)^i\) 的部分。
考虑 KMP 求 border 的方法,可以求字符串的最小 period,放到这里可以 \(O(|S|)\) 求循环节,那我们在 KMP 的时候求出每个前缀最大可以延展到哪里,然后再返回来枚举 \(AB\) 串的长度,就可以直接求满足条件的 \({(AB)}^iC\) 部分并进行累加。
在这里累加的时候:
假设我们枚举到了 \(S_{1 \sim i}\) 作为 \(AB\),并且它可以延展到 \((AB)^k\),那么我们可以分奇偶来求:
设 \(suf_i\) 为 \(i \sim |S|\) 的后缀中出现奇数次的字符的数量。
- 
延展到 \((AB)^j,j \equiv 1 \pmod 2\)。
那么所有 \((AB)^j\) 的贡献都是 \(pre_{i-1,suf_{i+1}}\),因为任意两个循环节合在一起后出现奇数次的字符的数量都为 \(0\)。
 - 
延展到 \((AB)^j,j \equiv 0 \pmod 2\)。
同理,所有 \((AB)^j\) 的贡献都是 \(pre_{i-1,suf_{2i+1}}\)。
 
那么就可以分奇偶来计算。
时间复杂度 \(O(\sum|S|C)\),\(C\) 为字符总数。
启示
(记得检查文件是不是开了……)
做有关循环节的题目时,可以用 KMP 优化求循环节,再返回来从循环节的角度切入题目。
代码
#define Plus_Cat "string"
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define ll long long
#define RCL(a,b,c,d) memset(a,b,sizeof(c)*(d))
#define FOR(i,a,b) for(int i(a);i<=(int)(b);++i)
#define DOR(i,a,b) for(int i(a);i>=(int)(b);--i)
#define tomin(a,...) ((a)=min({(a),__VA_ARGS__}))
#define tomax(a,...) ((a)=max({(a),__VA_ARGS__}))
#define EDGE(g,i,x,y) for(int i(g.h[x]),y(g[i].v);~i;y=g[i=g[i].nxt].v)
using namespace std;
constexpr int N((1<<20)+10),C(26);
char s[N];
int Cas,n;
int mx[N],suf[N],nxt[N];
int pre[N][C+1];
ll ans;
int Cmain() {
	scanf("%s",s+1),n=strlen(s+1),ans=0;
	auto Init_for_Suf=[&]() -> void {
		int sta(0);
		suf[n+1]=0;
		DOR(i,n,1)suf[i]=suf[i+1]+((sta^=1<<(s[i]-'a'))&1<<(s[i]-'a')?1:-1);
	};
	auto Init_for_Pre=[&]() -> void {
		int sta(0),cnt(0);
		FOR(i,1,n) {
			cnt+=((sta^=1<<(s[i]-'a'))&1<<(s[i]-'a')?1:-1);
			FOR(j,0,26)pre[i][j]=pre[i-1][j]+(j>=cnt);
		}
	};
	auto Next=[&]() -> void {
		int it(-1);
		nxt[0]=-1,RCL(mx+1,0,int,n);
		FOR(i,1,n) {
			while(~it&&s[it+1]!=s[i])it=nxt[it];
			nxt[i]=++it;
		}
		FOR(i,1,n-1)if(nxt[i]&&i%(i-nxt[i])==0)mx[i-nxt[i]]=i;
	};
	Init_for_Suf(),Init_for_Pre(),Next();
	FOR(i,2,n-1) {
		if(mx[i]||(nxt[i]&&i%(i-nxt[i])==0&&mx[i-nxt[i]])) {
			int len((mx[i]?mx[i]:mx[i-nxt[i]])/i);
			int odd(pre[i-1][suf[i+1]]),even(len>1&&(i<<1|1)<=n?pre[i-1][suf[i<<1|1]]:0);
			ans+=(ll)(((len-1)>>1)+1)*odd+(ll)(len>>1)*even;
		} else ans+=pre[i-1][suf[i+1]];
	}
	printf("%lld\n",ans);
	return 0;
}
int main() {
#ifdef Plus_Cat
	freopen(Plus_Cat ".in","r",stdin),freopen(Plus_Cat ".out","w",stdout);
#endif
	for(scanf("%d",&Cas); Cas; --Cas)Cmain();
	return 0;
}

                
            
        
浙公网安备 33010602011771号