[LOJ2133] 「NOI2015」品酒大会(后缀数组+并查集)

题意

  • 给你一个字符串 S S S,对于 r ∈ [ 0 , n ) r\in[0,n) r[0,n),求有多少对位置 ( i , j ) (i,j) (i,j)满足 ∀ k ∈ [ 0 , r ] \forall k\in[0,r] k[0,r],有 S i + k = S j + k S_{i+k}=S_{j+k} Si+k=Sj+k,并求出这些位置中任意两位置权值最大值。

首先如果两个位置对 r r r满足条件,那么它们对 k ∈ [ 0 , r ) k\in[0,r) k[0,r)也是满足条件的,我们只需要考虑恰好为 r r r时的答案,再做一遍后缀和与后缀最大值就好了。

我们先对这个串建出后缀数组,那么每个 r r r实际上就是两个后缀的最长公共前缀的长度,又因为两个后缀的最长公共前缀时对于连续一段排名的高度数组取最小值,所以我们可以把每个位置的权值映射到后缀对应的排名后按高度数组进行分类来统计答案。我们可以用并查集来维护这个过程,一个并查集内的元素一定满足任意两个元素的最长公共前缀长度都大于等于 r r r,那么每次合并两个并查集的时候只要统计新增加的答案对数即可,复杂度 O ( n log n ) O(\text {n log n}) O(n log n)

#include <bits/stdc++.h>

#define ll long long 
#define chkmax(a, b) (a < b ? a = b, 1 : 0)
#define chkmin(a, b) (a > b ? a = b, 1 : 0)
#define For(i, a, b) for (int i = a; i <= b; ++ i)
#define Forr(i, a, b) for (int i = a; i >= b; -- i)

using namespace std;

const int N = 3e5 + 7; 

int n, a[N]; 

char S[N];

struct Suffix_Array {

	int SA[N], rk[N << 1], tp[N << 1];
	int tong[N], m, height[N];

	void Radix_Sort() {
		For(i, 1, m) tong[i] = 0; 
		For(i, 1, n) ++ tong[rk[tp[i]]];
		For(i, 2, m) tong[i] += tong[i - 1];
		Forr(i, n, 1) SA[tong[rk[tp[i]]] --] = tp[i];
	}

	void build() {
		For(i, 1, n) m = max(m, rk[i] = S[i]), tp[i] = i;
		Radix_Sort();
		for (int len = 1; len < n; len <<= 1) {
			int cnt = 0; 
			For(i, n - len + 1, n) tp[++ cnt] = i; 
			For(i, 1, n) if (SA[i] > len) tp[++ cnt] = SA[i] - len; 
			Radix_Sort(), swap(rk, tp), rk[SA[1]] = m = 1; 
			For(i, 2, n) {
				if (tp[SA[i]] ^ tp[SA[i - 1]] || tp[SA[i] + len] ^ tp[SA[i - 1] + len]) ++ m; 
				rk[SA[i]] = m; 
			}
			if (m == n) break; 
		}
		For(i, 1, n) {
			int now = max(0, height[rk[i - 1]] - 1);
			while (now < n && S[i + now] == S[SA[rk[i] - 1] + now]) ++ now; 
			height[rk[i]] = now; 
		}
	}

} SA; 

ll ans1[N], ans2[N];

struct node {

	int h, x, y;

	bool operator < (const node &T) const {
		return h > T.h;
	}

} A[N];

struct Union_Find_Set {
	
	int fa[N], sz[N], mx[N], mn[N];

	int find(int x) { return x == fa[x] ? x : fa[x] = find(fa[x]); } 

	void merge(int now, int x, int y) { 
		x = find(x), y = find(y);
		if (x ^ y) {
			ans1[now] += 1ll * sz[x] * sz[y];
			chkmax(ans2[now], max(1ll * mx[x] * mx[y], 1ll * mn[x] * mn[y]));
			chkmax(mx[x], mx[y]), chkmin(mn[x], mn[y]), sz[x] += sz[y], fa[y] = x;
		}
	}

} Set; 

int main() {

	//freopen("2133.in", "r", stdin);
	//freopen("2133.out", "w", stdout);

	scanf("%d%s", &n, S + 1), SA.build();
	For(i, 1, n) scanf("%d", &a[i]);

	For(i, 2, n) A[i - 1] = (node) {SA.height[i], i - 1, i}; 
	For(i, 1, n) { 
		Set.fa[i] = i, Set.sz[i] = 1; 
		Set.mx[i] = Set.mn[i] = a[SA.SA[i]];
		ans2[i] = LLONG_MIN;
	}

	sort(A + 1, A + n);
	Forr(i, A[1].h, 0) {
		ans1[i] += ans1[i + 1];
		chkmax(ans2[i], ans2[i + 1]);
		for (static int j = 1; j < n && A[j].h == i; ++ j) 
			Set.merge(i, A[j].x, A[j].y);
	}

	For(i, 0, n - 1) {
		if (ans1[i]) printf("%lld %lld\n", ans1[i], ans2[i]);
		else puts("0 0");
	}

	return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值