NOIP2020 T2

题意:把一个字符串切成形如 \(ABAB......ABC\) 的形式,其中 \(A,B,C\) 都是非空子串且 \(F(A) \le F(C)\)\(F(S)\) 表示 \(S\) 中出现奇数次的数的个数)。问方法数。(字符串长度 \(|S| \le 2^{20}\)

思路:

1.看到一道题可以首先想暴力:按照题意模拟就行了,先枚举 \(A\) 的长度,再枚举 \(B\) 的长度,最后枚举 \(AB\) 的重复次数,剩下的就是 \(C\),然后判断行不行,(就是判断 \(AB\) 能否重复这么多次)\(~~~~~~\)(至少 \(\Theta(n^4)\)

小优化1.判断 \(AB\) 行不行时,用哈希,少个 \(n\)

小优化2.很明显, \(A\)\(C\) 肯定是前后缀,所以可以 \(\Theta(n)\) 预处理掉!

小优化3.又枚举 \(A\) 又枚举 \(B\) 太浪费了。我们想到一般这种可以少枚举一个再用数据结构,所以枚举 \(AB\) 的总长度,对于一种现在枚举的情况,现在我们要求的就是从1到 \(i - 1\) 中取 \(A\),使 \(F(A) \le F(C)\) 的个数。很明显用个树状数组搞定。

小优化4.我们发现,当枚举重复次数时,如果在原有次数上增添一个 “\(ABAB\)", 字符串中数出现的奇偶性不变,所以 \(A\) 的取值的可行性也不会改变(即满足 \(F(A) \le F(C)\) 的个数是定值)。所以我们不需要每次都用树状数组求,这样就带两个 \(log\) 了。只需要算出重复1和2次的情况,后面 \(\Theta(1)\) 计算贡献即可。

用这种方法,这题好像就完了?

上代码:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
const int N = 2e6 + 10, base = 13331, mod = 998244353;
int T, n;
char s[N];
ll ans;
int c[30], now, pre[N], suf[N];
ull pw[N], hx[N];
int tr[30];
inline int lb(int x)
{
	return x & -x;
}
inline void add(int x)
{
	++x;
	while(x <= 27)
	{
		++tr[x];
		x += lb(x);
	}
}
inline int qry(int x)
{
	int res = 0;
	++x;
	while(x)
	{
		res += tr[x];
		x -= lb(x);
	}
	return res;
}
inline ull gethx(int l, int r)
{
	return hx[r] - hx[l - 1] * pw[r - l + 1];
}
int w[2];
int main()
{
	scanf("%d", &T);
	pw[0] = 1;
	for(int i = 1; i <= 2e6; ++i) pw[i] = pw[i - 1] * base;
	while(T--)
	{
		scanf("%s", s + 1); n = strlen(s + 1);
		memset(c, 0, sizeof c);
		now = 0;
		for(int i = 1; i <= n; ++i)
		{
		    hx[i] = hx[i - 1] * base + s[i]; //哈希判断字符串相等 
			c[s[i] - 'a'] ^= 1;
			if(c[s[i] - 'a']) ++now;
			else --now;
			pre[i] = now; //预处理前后缀权值 (A/C) 
		}
		memset(c, 0, sizeof c);
		now = 0;
		for(int i = n; i; --i)
		{
			c[s[i] - 'a'] ^= 1;
			if(c[s[i] - 'a']) ++now;
			else --now;
			suf[i] = now;
		}
		memset(tr, 0, sizeof tr);
		ans = 0;
		for(int i = 2; i < n; ++i)
		{
			add(pre[i - 1]); //枚举AB长度,AB中有多少个A符合用树状数组算 
			w[1] = qry(suf[i + 1]); //奇偶分别算,后面奇偶性相同O(1)算答案 
			if((i << 1) < n)
			    w[0] = qry(suf[(i << 1) + 1]); 
			for(int j = 1; i * j < n; ++j)
			 	if(hx[i] == gethx(i * (j - 1) + 1, i * j))
			    	ans += w[j & 1];
			    else break; //枚举AB最多重复次数
		}
		cout << ans << '\n';
	}
	return 0;
} 
posted @ 2022-07-20 17:08  Faker_yu  阅读(33)  评论(0)    收藏  举报