[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;
}
posted @ 2020-12-22 20:21  月夭  阅读(379)  评论(0编辑  收藏  举报