bzoj 3473 字符串&bzoj 3277 串 后缀数组

题面

题目传送门
双倍经验传送门

解法

我还是太菜了……3277总时限10s,然而T了……

  • 首先显然是把这些串拼起来中间用分隔符隔开,然后构造出后缀数组。
  • 然后我们枚举子串的开头部分。可以发现,最大能贡献到答案中的长度是满足单调性的,所以我们可以考虑二分这个长度,假设为 l e n len len
  • 那么我们就可以求出这个后缀所在的最长的区间使得这个区间中后缀两两的 l c p lcp lcp不小于 l e n len len,假设是区间 [ l , r ] [l,r] [l,r]。然后我们只要看区间 [ l , r ] [l,r] [l,r]中是否有 k k k个不同的数就可以了。
  • 对于如何求解 [ l , r ] [l,r] [l,r]中是否有 k k k个不同的数,因为 k k k是一直不变的,所以我们可以对于每一个区间的起点 l l l求出最小的 r r r使得 [ l , r ] [l,r] [l,r]中有至少 k k k种数。这个可以直接扫一遍求出。
  • 时间复杂度: O ( n log ⁡ 2 n ) O(n\log^2 n) O(nlog2n)
  • 应该有复杂度更优的做法,但是可能要后缀自动机了……我太菜了并不会……

代码

#include <bits/stdc++.h>
#define inf 1 << 30
#define N 200010
using namespace std;
int n, m, sum, c[N], s[N], y[N], sa[N], ans[N], col[N], nxt[N], lim[N], rnk[N], f[N][21];
int lcp(int i, int j) {
	int l = i + 1, r = j, t = log2(r - l + 1);
	return min(f[l][t], f[r - (1 << t) + 1][t]);
}
void Sort() {
	for (int i = 1; i <= m; i++) s[i] = 0;
	for (int i = 1; i <= n; i++) s[rnk[i]]++;
	for (int i = 1; i <= m; i++) s[i] += s[i - 1];
	for (int i = n; i; i--) sa[s[rnk[y[i]]]--] = y[i];
}
void build(string st) {
	n = st.size() - 1, m = 200;
	for (int i = 1; i <= n; i++) rnk[i] = st[i], y[i] = i;
	Sort(); int len = 0;
	for (int k = 1; k <= n; k <<= 1, m = len, len = 0) {
		for (int i = n - k + 1; i <= n; i++) y[++len] = i;
		for (int i = 1; i <= n; i++) if (sa[i] > k) y[++len] = sa[i] - k;
		Sort(), swap(rnk, y), rnk[sa[1]] = len = 1;
		for (int i = 2; i <= n; i++)
			rnk[sa[i]] = y[sa[i - 1]] == y[sa[i]] && y[sa[i - 1] + k] == y[sa[i] + k] ? len : ++len;
	}
	for (int i = 1, k = 0; i <= n; i++) {
		if (rnk[i] == 1) continue;
		if (k) k--; int j = sa[rnk[i] - 1];
		while (st[i + k] == st[j + k]) k++;
		f[rnk[i]][0] = k;
	}
	for (int j = 1; (1 << j) <= n; j++)
		for (int i = 1; i + (1 << j) - 1 <= n; i++)
			f[i][j] = min(f[i][j - 1], f[i + (1 << j - 1)][j - 1]);
}
void calc(int k) {
	int r = 0, tot = 0;
	for (int i = 1; i <= n; i++) {
		while (tot < k && r < n) {
			s[col[++r]]++;
			if (s[col[r]] == 1) tot++;
		}
		if (tot < k) lim[i] = inf; else lim[i] = r;
		s[col[i]]--; if (s[col[i]] == 0) tot--;
	}
}
bool check(int x, int len) {
	int l = 1, r = x - 1, tl = x;
	while (l <= r) {
		int mid = (l + r) >> 1;
		if (lcp(mid, x) >= len) tl = mid, r = mid - 1;
			else l = mid + 1;
	}
	l = x + 1, r = n; int tr = x;
	while (l <= r) {
		int mid = (l + r) >> 1;
		if (lcp(x, mid) >= len) tr = mid, l = mid + 1;
			else r = mid - 1;
	}
	return lim[tl] <= tr;
}
int main() {
	ios::sync_with_stdio(false);
	string st = "$"; int tot, k; cin >> tot >> k;
	for (int i = 1; i <= tot; i++) {
		string str; cin >> str; st = st + str;
		if (i < tot) st = st + '#';
	}
	build(st); int cnt = tot, las = n + 1;
	for (int i = n; i; i--)
		if (st[i] == '#') cnt--, las = i; else nxt[rnk[i]] = las, col[rnk[i]] = cnt;
	memset(s, 0, sizeof(s)); calc(k);
	for (int i = 1; i <= n; i++) {
		if (st[sa[i]] == '#') continue;
		int l = 1, r = nxt[i] - sa[i], ret = 0;
		while (l <= r) {
			int mid = (l + r) >> 1;
			if (check(i, mid)) ret = mid, l = mid + 1;
				else r = mid - 1;
		}
		ans[col[i]] += ret;
	}
	for (int i = 1; i <= tot; i++) cout << ans[i] << ' '; cout << "\n";
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值