洛谷 P7114 / LOJ 3387 「NOIP2020」字符串匹配

洛谷传送门

LOJ 传送门

思路

\(|S| = n\)。首先求出 \(S\)\(\mathbf{Z}\) 函数数组 \(nxt\),考虑将 \(AB\) 视作一个整体的循环节,那么长度为 \(i\) 的循环节最多可以接上 \(\left\lfloor\dfrac{nxt_{i+1}}{i}\right\rfloor + 1\) 组。

这个理解起来很简单。根据定义,子串 \([i+1,i+nxt_{i+1}]\)\([1,nxt_{i+1}]\) 相同,即 \([1,i]\)\([i+1,2i]\) 相同,\([i+1,2i]\)\([2i+1,3i]\),相同,以此类推。

然后考虑题中 \(F(A) \le F(C)\) 的限制。枚举 \(AB\) 的长度,记 \(f(i,j)\)\([i,j]\) 中出现次数为奇数的字符数量。令循环节出现次数为 \(t\),发现只用考虑 \(t\) 为奇数或偶数的情况,因为 \(C(AB)^x\)\(C(AB)^{x+2}\) 的字符出现次数的奇偶情况相同。设 \(|A| = j\ (j < i)\),当 \(t\) 为奇数,则 \(j\) 可行当且仅当 \(f(1,j) \le f(i+1,n)\);当 \(t\) 为偶数,则 \(j\) 可行当且仅当 \(f(1,j) \le f(2i+1,n) \iff f(1,j) \le f(1,n)\)。用树状数组维护当前所有前缀 \([1,k]\ (k < i)\) 中出现次数为奇数的字符数量为 \(x\) 的前缀个数。易知 \(t\) 为奇数时有 \(t - \left\lfloor\dfrac{t}{2}\right\rfloor\) 种情况,为偶数时有 \(\left\lfloor\dfrac{t}{2}\right\rfloor\) 种情况,乘法原理计算即可。

时间复杂度 \(O(Tn \log 26)\)。实现细节见代码。

代码

code
/*

p_b_p_b txdy
AThousandMoon txdy
AThousandSuns txdy
hxy txdy

*/

#include <bits/stdc++.h>
#define pb push_back
#define fst first
#define scd second

using namespace std;
typedef long long ll;
typedef pair<ll, ll> pii;

const int maxn = 1050000;

int n, nxt[maxn], c[30], cnt1[30], cnt2[30];
char s[maxn];

int lowbit(int x) {
	return x & (-x);
}

void update(int x, int d) {
	for (int i = x; i <= 27; i += lowbit(i)) {
		c[i] += d;
	}
}

int query(int x) {
	int res = 0;
	for (int i = x; i; i -= lowbit(i)) {
		res += c[i];
	}
	return res;
}

void solve() {
	memset(c, 0, sizeof(c));
	memset(cnt1, 0, sizeof(cnt1));
	memset(cnt2, 0, sizeof(cnt2));
	scanf("%s", s + 1);
	n = strlen(s + 1);
	// 求 Z 函数
	nxt[1] = n;
	for (int i = 2, l = 0, r = 0; i <= n; ++i) {
		nxt[i] = (i > r) ? 0 : min(nxt[i - l + 1], r - i + 1);
		while (s[nxt[i] + 1] == s[nxt[i] + i]) {
			++nxt[i];
		}
		if (i + nxt[i] - 1 > r) {
			l = i;
			r = i + nxt[i] - 1;
		}
	}
	// 由于 C 非空,所以空一个位置给 C
	for (int i = 1; i <= n; ++i) {
		if (i + nxt[i] - 1 == n) {
			--nxt[i];
		}
	}
	for (int i = 1; i <= n; ++i) {
		++cnt2[s[i] - 'a'];
	}
	// 维护当前前缀和后缀中出现次数为奇数的字符个数
	int pre = 0, suf = 0;
	for (int i = 0; i < 26; ++i) {
		if (cnt2[i] & 1) {
			++suf;
		}
	}
	int sum = suf;
	ll ans = 0;
	for (int i = 1; i < n; ++i) {
		if (cnt1[s[i] - 'a'] & 1) {
			--pre;
		} else {
			++pre;
		}
		++cnt1[s[i] - 'a'];
		if (cnt2[s[i] - 'a'] & 1) {
			--suf;
		} else {
			++suf;
		}
		--cnt2[s[i] - 'a'];
		// A,B 非空,因此空至少一个位置给 B
		if (i > 1) {
			ll x = nxt[i + 1] / i + 1;
			ans += (x - x / 2) * query(suf + 1) + (x / 2) * query(sum + 1);
		}
		update(pre + 1, 1);
		// 因为下表可能为 0,所以树状数组下标整体加一
	}
	printf("%lld\n", ans);
}

int main() {
	int T = 1;
	scanf("%d", &T);
	while (T--) {
		solve();
	}
	return 0;
}
posted @ 2022-06-21 20:41  zltzlt  阅读(62)  评论(0编辑  收藏  举报