[BZOJ 1692] [Usaco2007 Dec] 队列变换 【后缀数组 + 贪心】

---恢复内容开始---

题目链接:BZOJ - 1692

 

题目分析

首先,有个比较简单的贪心思路:如果当前剩余字符串的两端字母不同,就选取小的字母,这样显然是正确的。

然而若两端字母相同,我们怎么选取呢?

这时我们要从两端分别向内部比较,看那一端向内的字符串字典序小。

比如这个字符串 ABCDBA,从左端向内是 ABC.. 从右端向内是 ABD... 所以就选取左端的字符。

这样直接比较是 O(n^2) 的,我们可以使用后缀数组的 Rank 数组来比较。

我们在字符串后加上分隔符,然后再将字符串反转接在后面,求后缀数组的 Rank 数组。

这样就可以快速比较一个前缀,一个后缀的字典序大小了,具体见代码。

比如 ABCDBA ,就存成 ABCDBA#ABDCBA 。

 

代码

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <algorithm>

using namespace std;

const int MaxL = 60000 + 15;

int n;
int A[MaxL], Rank[MaxL], SA[MaxL];
int VA[MaxL], VB[MaxL], VC[MaxL], Sum[MaxL];

char S[MaxL], Sout[MaxL];

inline bool Cmp(int *a, int x, int y, int l) {
	return (a[x] == a[y]) && (a[x + l] == a[y + l]);
}

void DA(int *A, int n, int m) {
	int *x, *y, *t;
	x = VA; y = VB;
	for (int i = 1; i <= m; ++i) Sum[i] = 0;
	for (int i = 1; i <= n; ++i) ++Sum[x[i] = A[i]];
	for (int i = 2; i <= m; ++i) Sum[i] += Sum[i - 1];
	for (int i = n; i >= 1; --i) SA[Sum[x[i]]--] = i;
	int p, q;
	p = 0;
	for (int j = 1; p < n; j <<= 1, m = p) {
		q = 0;
		for (int i = n - j + 1; i <= n; ++i) y[++q] = i;
		for (int i = 1; i <= n; ++i) {
			if (SA[i] <= j) continue;
			y[++q] = SA[i] - j;
		}
		for (int i = 1; i <= n; ++i) VC[i] = x[y[i]];
		for (int i = 1; i <= m; ++i) Sum[i] = 0;
		for (int i = 1; i <= n; ++i) ++Sum[VC[i]];
		for (int i = 2; i <= m; ++i) Sum[i] += Sum[i - 1];
		for (int i = n; i >= 1; --i) SA[Sum[VC[i]]--] = y[i];
		t = x; x = y; y = t;
		x[SA[1]] = 1; p = 1;
		for (int i = 2; i <= n; ++i) 
			x[SA[i]] = Cmp(y, SA[i], SA[i - 1], j) ? p : ++p;
	}
	for (int i = 1; i <= n; ++i) Rank[SA[i]] = i;
}

int main() 
{
	scanf("%d", &n);
	for (int i = 1; i <= n; ++i) {
		cin >> S[i];
		A[i] = S[i] - 'A' + 1;
		A[2 * n - i + 2] = A[i];
	}
	A[n + 1] = 27;
	A[n * 2 + 2] = 28;
	DA(A, n * 2 + 2, 28);
	int l = 1, r = n, Top = 0;
	while (l <= r) {
		if (S[l] != S[r]) {
			if (S[l] < S[r]) { 
				Sout[++Top] = S[l];
				++l;
			} 
			else {
				Sout[++Top] = S[r];
				--r;
			}
			continue;
		}
		if (l == r) {
			Sout[++Top] = S[l];
			break;
		}
		if (Rank[l] < Rank[n * 2 - r + 2]) {
			Sout[++Top] = S[l];
			++l;
		}
		else {
			Sout[++Top] = S[r];
			--r;
		}
	}
	for (int i = 1; i <= Top; ++i) {
		printf("%c", Sout[i]);
		if (i % 80 == 0) printf("\n");
	}
	return 0;
}

  

---恢复内容结束---

转载于:https://www.cnblogs.com/JoeFan/p/4214919.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值