[NOIP2020]字符串匹配
首先考虑暴力:令 \(T=AB\),从小到大枚举 \(T\) 的长度,找到最短的 \(C_1\) ,枚举有多少个 \(T\) 的真前缀 \(A\) 满足 \(F(A) \leqslant F(C_1)\),
之后令 \(C_2=ABC\),再令$ C_3=ABABC$……以此类推
考虑优化。结合上面的暴力分析,容易想到 \(F(C_k)\) 的前面的所有 \(ABAB\)均不会对其产生贡献,故有 \(F(C_{2n-1})=F(C_1)\), \(F(C_{2n})=F(C_2)\)
所以,$ A\(,\)B\(,\)C$ 是一组解就意味着 \(A\),\(B\),\(ABABC\)也是一组解(如果 \(AB\) 的重复次数 \(\geqslant 3\)),
那么我们无需枚举所有的 \(C_k\),而只需计算 \(C_1\) 与 \(C_2\) 解的数量,再分别乘上有多少$ C_k$ 与其相同即可
最后我们发现时间复杂度的瓶颈在于找到最短的 \(C\),看出这个问题本质是求 \(T\) 的最大重复次数,使用字符串 \(Hash\) 即可
此算法是 \(n (\ln n+\log_2 26)\)的
——以上均摘自开头说的博客em
求\(C_1\) \(C_2\)的解的数量, 我们只需要找到\(T\)的真前缀中有多少个满足条件的即可 应该...能想到用树状数组来维护叭
对于字符串\(S\)的每个后缀的 出现奇数次的字母的数量 我们也可以提前处理出来 这样就能很快确定C的
出现奇数次的字母的数量啦
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 1 << 20 + 5;
ll n, ans, jcnt;//jcnt代表当前有多少个奇数次的字符数
char s[N];
ll bit[30];//bit[i]代表有字符串S有多少个的前缀有不超过i个奇数次的字符的数量
ll discnt[N];//discnt[i]表示S[i - n]有的多少个奇数次的字符的数量
bool isj[30], disj[30];//isj[1 - 26]分别表示a - z的数量是否为奇数次, disj意义相同,只不过是倒序的
unsigned long long h[N], p;//h[]: 哈希值
int lowbit(int x) {
return x & (-x);
}
void add(int x) {
x++;//因为树状数组的下标不能为0所以在这里统一加个1
while(x <= 27) {
bit[x]++;
x += lowbit(x);
}
}
ll ask(int x) {
x++;
ll sum = 0;
while(x) {
sum += bit[x];
x -= lowbit(x);
}
return sum;
}
int main() {
ios :: sync_with_stdio(0);
cin >> n;
while(n--) {
ans = 0;
p = 233 * 233;
memset(disj, 0, sizeof(disj));
memset(isj, 0, sizeof(isj));
memset(bit, 0, sizeof(bit));
cin >> s + 1;
ll len = strlen(s + 1);
discnt[len] = 1;//最后一个字符重复了一次
disj[s[len] - 'a'] = 1;
for(int i = len - 1; i >= 1; i--) {
disj[s[i] - 'a'] = !disj[s[i] - 'a'];
discnt[i] = discnt[i + 1] + (disj[s[i] - 'a'] == 1 ? 1 : -1);
}
for(int i = 1; i <= len; i++)
h[i] = h[i - 1] * 233 + s[i];
add(1);
jcnt = 1;
isj[s[1] - 'a'] = 1;
for(int i = 2; i < len; i++) { //枚举AB长度
ll x = 1; //x代表循环节长度
while(i * (x + 1) <= len) {
int temp = i * (x + 1);
if(h[temp] - h[temp - i] * p == h[i]) x++;
else break;
}
if(x * i == len) ans += bit[1] * ((x - 1) / 2);//当x * i == len时C不能是空串所以最短的C应该是ABAB, 会比其他的情况少一种
else ans += ask(discnt[x * i + 1]) * ((x - 1) / 2 + 1);//C1, C3, C5...的解的数量
ans += ask(discnt[(x - 1) * i + 1]) * (x / 2);//C2, C4, C6...的解的数量
isj[s[i] - 'a'] = !isj[s[i] - 'a'];
add(isj[s[i] - 'a'] ? ++jcnt : --jcnt);//最后再更新, 否则可能会导致B为空串
p *= 233;//T的长度增加, p也应该增加
}
printf("%lld\n", ans);
}
return 0;
}