NOIP2020 T2 字符串匹配【题解】
NOIP2020 T2 字符串匹配
首先声明
这篇题解存在大多数让我这种人看懂的废话,如果想要速通,请另寻他解
题目简化
定义字符串乘法为 \(AB\) 为把两个字符串拼起来,定义阶乘 \(A^i\) 表示 \(\prod_{1}^i A\)
再定义 \(F(S)\) 为 \(S\) 中出现奇数次字符的数量
现给定一个字符串 \(S\),求找到 \(S=(AB)^iC\) 的方案数(\(|A,B,C|>0\))满足 \(F(A)\le F(C)\)
浅浅谈一下吧
我们观察这道题的数据范围,发现只允许 \(\mathcal{O}(n\log n)\) 的算法通过,那我们就会有一个思路
枚举 \(C\) 看看前面的合不合法,合法就加
那我们怎么知道这段区间合不合法呢?
容易想到,把 \(AB\) 看成同一个字符串,判断这段区间是否为 \(S^i\) 即可
问题又来了,怎么判断 \(S^i\) 呢?
联想到了,最小周期为 \(i-\pi_i\),记为 \(p\)(使用 \(KMP\) 求解)
那只需要满足 \(p|i\) 就可以满足 \(i\) 可以被若干个 \(p\) 拼成
于是思路就来了,我们枚举 \(i\) 判断当前的 \(|S|^i\) 的周期是否合法
不过还有另外一个限制,即 \(p\) 要能拼成 \(S\) 才行(主人公是 \(S\)),即需要满足 \(p\ |\ |S|\)
那两个都需要判断吗?不需要,因为 \(i\) 本身就是 \(|S|\) 的倍数,代码如下
for (int j = i + i;/*避免出现1次方,会错*/ j < n; j += i) {
int q = j - N[j];
if (i % q == 0 && j / q > 1) //保证x次方(x>1)
统计答案
}
那怎么统计答案呢?
注意到,我们可以记一个数组 \(Per_i\) 表示当前位置满足奇数字母大于等于 \(i\) 的字符串数量
另外,\(A\) 是个前缀,说明可以直接进行更新,每过一个位置,就把 \(A[0...i]\) 加进合法的答案里
但是,我们需要预处理前缀数组 \(Pre_i\) 和后缀数组 \(Suf_i\),即可完成这道题
注意细节
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 2e6 + 7;
typedef long long ll;
#define Ms(a, k) (memset(a, k, sizeof(a)))
int T;
char S[MAXN];
int n, N[MAXN];
int t[MAXN], Pre[MAXN], Suf[MAXN], Per[MAXN];
signed main () {
ios :: sync_with_stdio(false);
cin.tie(NULL); cout.tie(NULL);
for (cin >> T; T; T --) {
cin >> S + 1; n = strlen(S + 1);
N[1] = 0; Ms(Pre, 0); Ms(Suf, 0); Ms(Per, 0);
for (int i = 2; i <= n; i ++) {
int p = N[i - 1];
while (p != 0 && S[p + 1] != S[i]) p = N[p];
if (S[p + 1] == S[i]) p ++; N[i] = p;
}
Ms(t, 0);
for (int i = 1; i <= n; i ++)
if ((++ t[S[i] - 'a' + 1]) & 1) Pre[i] = Pre[i - 1] + 1;
else Pre[i] = Pre[i - 1] - 1;
Ms(t, 0);
for (int i = n; i >= 1; i --)
if ((++ t[S[i]] - 'a' + 1) & 1) Suf[i] = Suf[i + 1] + 1;
else Suf[i] = Suf[i + 1] - 1;
ll ans = 0;
for (int i = 1; i < n;/*C不为空*/ i ++) {
if (i >= 2) { //B不为空
ans += (ll)Per[Suf[i + 1]];//1次方
for (int j = i + i;/*避免出现1次方,会错*/ j < n; j += i) {
int q = j - N[j];
if (i % q == 0 && j / q > 1) //保证x次方(x>1)
ans += (ll)Per[Suf[j + 1]];
}
}
for (int j = Pre[i]; j <= 30; j ++) Per[j] ++; //A是前缀,将A[1...i]加进去
}
cout << ans << '\n';
}
return 0;
}
完结撒花✿✿ヽ(°▽°)ノ✿