[NOIP2020]字符串匹配 题解
Preface
怎么题解里全是扩展 KMP 啊,好像就我不会这个东西QAQ。
只能写写大佬们都看不上的哈希+调和了>_<
upd:以后会更新正解。
upd:exKMP 学不会,咕了哈哈(逃。
Analysis
令 \(N=| S|\)。
首先发现,枚举 \(C\) 再判断前缀消耗的时间很多,这样行不通。
转向考虑枚举 \(AB\),得出所有的 \((AB)^i\),不难发现可以用哈希+调和做到 \(O(N\ln N)\)。
现在考虑 \(f(A)\le f(C)\) 的限制。
设 \(pre(i)=f(S_{1\sim i}),suf(i)=f(S_{i+1\sim n})\)。
当前枚举到 \(AB\) 的长度为 \(x\),则 \(AB\) 对答案的贡献为 \(\sum\limits_{i}\sum\limits_{j=1}^x [pre(j)\le suf(x\times i+1)]\)。
预处理出 \(pre(1\sim n),suf(1\sim n)\),用树状数组维护,时间复杂度为 \(O(TN\ln N\log N)\)。
虽然常数小,但只有 \(84\texttt{pts}\)。
仔细地思考一下,这个东西真的必须要用树状数组维护吗?
设 \(sum(i,j)= \sum\limits_{k=1}^i [pre(i)\le j]\),则 \(AB\) 的贡献变为 \(\sum\limits_{i}sum(x,x\times i+1)\)。
而 \(sum\) 数组显然可以用前缀和维护。
时间复杂度 \(O(T(N\ln N+N\times 26))\),常数别搞太大还是足以通过的。
Code
本地是 C++17
,结果 hash
给我爆关键字了QAQ。
// Problem: #3387. 「NOIP2020」字符串匹配
// Contest: LibreOJ
// URL: https://loj.ac/p/3387
// Memory Limit: 512 MB
// Time Limit: 1000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include <bits/stdc++.h>
using namespace std;
const int maxn = (1 << 20) + 5;
const int maxm = 30;
const int p = 131;
typedef long long ll;
char s[maxn];
int n,sum[maxn][maxm],Hash[maxn],pw[maxn],suf[maxn];
ll ans;
void work() {
scanf("%s",s + 1);
n = strlen(s + 1);
Hash[0] = 0;
pw[0] = 1;
int cnt[maxm] = {0};
int cur = 0;
//calc pre
for(int i = 1;i <= n;++ i) {
for(int j = 0;j < maxm;++ j)sum[i][j] = 0;
pw[i] = pw[i - 1] * p;
Hash[i] = Hash[i - 1] * p + s[i];
if(cnt[s[i] - 'a'] & 1)-- cur;
else ++ cur;
++ cnt[s[i] - 'a'];
sum[i][cur] = 1;
}
for(int i = 1;i <= n;++ i) {
for(int j = 1;j <= 26;++ j) {
sum[i][j] += sum[i][j - 1];
}
for(int j = 0;j <= 26;++ j) {
sum[i][j] += sum[i - 1][j];
}
}
//calc suffix
memset(cnt , 0 , sizeof(cnt));
suf[n + 1] = 0;
for(int i = n;i;-- i) {
if(cnt[s[i] - 'a'] & 1)suf[i] = suf[i + 1] - 1;
else suf[i] = suf[i + 1] + 1;
++ cnt[s[i] - 'a'];
}
ans = 0;
for(int i = 2;i < n;++ i) {
for(int j = i;j < n;j += i) {
if(Hash[i] != Hash[j] - Hash[j - i] * pw[i])break ;
ans += sum[i - 1][suf[j + 1]];
}
}
printf("%lld\n",ans);
return ;
}
int main() {
freopen("string.in","r",stdin);
freopen("string.out","w",stdout);
int T;
scanf("%d",&T);
while(T --)work();
return 0;
}
完结撒花✿✿ヽ(°▽°)ノ✿
(回看当年赛场上瞎搞的 \(48\texttt{pts}\) 暴力感慨万千qwq)