[NOIP2020]字符串匹配 题解

传送门QAQ

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)

posted @ 2022-08-03 21:25  ImALAS  阅读(113)  评论(0编辑  收藏  举报