LOJ#3387 字符串匹配 题解

分析

规定以下讨论中,字符串的下标从 \(1\) 开始。

注意到 \((AB)^K\) 一定是一段前缀,\(C\) 一定是一段后缀,而 \(AB\) 又一定是 \((AB)^k\) 的前缀,且满足连续出现 \(k\) 次。

考虑 \(S\)\(z\) 函数。

如果知道了 \(AB\) 的长度和 \(k\),那么就能计算出对应的 \(C\)。设当前 \(AB\) 长度为 \(i\),那么最多的循环次数为 \[ \lfloor \frac{z_{i+1}}{i} \rfloor + 1 \] 感性理解一下,显然是对的

当然这是“至多”,我们可以规定循环次数为任何一个小于最多次数,大于 \(0\) 的整数。

注意到对于奇偶性相同的 \(k\),出现次数为奇数的字符个数是相等的,所以只需要分奇偶讨论。当 \(k\) 为奇数时,个数不变,当 \(k\) 为偶数时,不存在出现次数为奇数的字符。

\(g(l,r)\)\(S[l,r]\) 中出现次数为奇数的字符的数量。设当前 \(AB\) 长度为 \(i\)\(|A| = j\)

首先一定存在 \(F(A) \le F(C)\)\(F(A) = g(1,j)\)

  • 如果 \(k\) 是个奇数,那么就有 \(g(1,j) \le g(i+1,n)\)。尽管 \(C\) 中具体有多少个出现次数为奇数的字符是未知的,但是 \((AB)^k\)\([i+1,n]\) 的出现次数一定为偶数次,所以不会产生影响,\(g(i+1,n)\) 就是 \(F(C)\)
  • 否则,由于 \((AB)^k\) 不存在这类字符,所以 \([1,ki]\) 就不存在出现次数为奇数的字符,因此 \(F(C) = g(ki+1,n) = g(1,n) \Longrightarrow g(1,j) \le g(1,n)\)

如何维护这类信息?使用树状数组。设 \(C(x)\) 表示长度小于 \(i\) 且这类字符的个数小于等于 \(x\) 的前缀的数量。

同时用变量 \(pre\) 表示 \([1,i]\) 中这类字符的个数,\(suf\) 表示 \([i+1,n]\) 中的,\(all\) 表示 \(g(1,n)\)

对于 \(t = \lfloor \frac{z_{i+1}}{i} \rfloor + 1\),奇数的情况有 \(t - \lfloor \frac{t}{2} \rfloor\),偶数则有 \(\lfloor \frac{t}{2} \rfloor\)。对于 \(i\) \[ (t - \lfloor \frac{t}{2} \rfloor) \cdot C(suf) + \lfloor \frac{t}{2} \rfloor \cdot C(all) \] 然后插入 \(pre\) 即可。

CODE

#include<bits/stdc++.h>
using namespace std;
#define int long long
int read() {
	int a=0, f=1; char c=getchar();
	while(!isdigit(c)) {
		if(c=='-') f=-1;
		c=getchar();
	}
	while(isdigit(c)) a=a*10+c-'0', c=getchar();
	return a*f;
}
const int N=(1<<20)+2;
int t, n, z[N], p[30], q[30], c[30];
char s[N];
void modify(int x,int y) { for(;x<=27;x+=x&-x) c[x]+=y; }
int query(int x) {
	int y=0;
	for(;x;x-=x&-x) y+=c[x];
	return y;
}
void Z() {
	int l=0, r=0;
	z[0]=n;
	for(int i=1;i<n;++i) {
		if(i<=r&&z[i-l]<r-i+1) z[i]=z[i-l];
		else {
			z[i]=max(r-i+1,0ll);
			while(i+z[i]<n&&s[z[i]]==s[i+z[i]]) ++z[i];
			if(r<i+z[i]-1) l=i, r=i+z[i]-1;
		}
	}
	for(int i=0;i<n;++i) if(i+z[i]==n) --z[i];
    // 由于c不是空串,所以i+z[i]必须小于n
}
void solve() {
	scanf("%s",s);
	n=strlen(s);
	memset(c,0,sizeof(c));
	memset(p,0,sizeof(p));
	memset(q,0,sizeof(q));
	Z();
	int ans=0, pre=0, suf=0;
	for(int i=0;i<n;++i) ++q[s[i]-'a'];
	for(int i=0;i<26;++i) if(q[i]&1) ++suf;
	int all=suf;
	for(int i=0;i<n-1;++i) {
		int d=s[i]-'a';
		++p[d], --q[d];
		if(p[d]&1) ++pre; else --pre;
		if(q[d]&1) ++suf; else --suf;
		if(i) {
			int t=z[i+1]/(i+1)+1;
            // 由于我的代码中i从0开始,所以要+1表示长度为i,和z[i+1]中的+1无关
			ans+=(t-t/2)*query(suf+1)+(t/2)*query(all+1);
            // 为了防止0下标,这些都要+1
		}
		modify(pre+1,1);
	}
	printf("%lld\n",ans);
}
signed main() {
	freopen("string.in","r",stdin);
	freopen("string.out","w",stdout);
	t=read();
	while(t--) solve();
}

LOJ#3387 字符串匹配 题解
https://yozora0908.github.io/2022/loj3387-solution/
作者
yozora0908
发布于
2022年8月20日
许可协议