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

题面

题目传送门

解法

每一次选的时候比较左端点为起点的后缀与右端点为起点的原串倒序的后缀哪一个字典序小,小的那个相应地移一下即可

感觉这比较绕啊,举个例子解释一下

假设当前的串为\(AABCAA\)\(l=1,r=6\)

那么只要比较\(AABCAA\)\(AACBAA\)哪一个字典序小,发现是\(AABCAA\)字典序小,所以将l+1

将原串反序接在原串的后面,然后求一遍rnk

每一次比较一下哪一个rnk小即可

时间复杂度:\(O(n\ log\ n)\)

代码

#include <bits/stdc++.h>
#define N 200010
using namespace std;
int n, st[N];
struct SuffixArray {
	int x[N], y[N], sa[N], rnk[N], cnt[N];
	void build() {
		int m = 26;
		for (int i = 1; i <= n; i++) cnt[x[i] = st[i]]++;
		for (int i = 2; i <= m; i++) cnt[i] += cnt[i - 1];
		for (int i = n; i; i--) sa[cnt[x[i]]--] = i;
		for (int k = 1; k <= n; k <<= 1) {
			int num = 0;
			for (int i = n - k + 1; i <= n; i++) y[++num] = i;
			for (int i = 1; i <= n; i++)
				if (sa[i] > k) y[++num] = sa[i] - k;
			for (int i = 1; i <= m; i++) cnt[i] = 0;
			for (int i = 1; i <= n; i++) cnt[x[i]]++;
			for (int i = 2; i <= m; i++) cnt[i] += cnt[i - 1];
			for (int i = n; i; i--) sa[cnt[x[y[i]]]--] = y[i], y[i] = 0;
			swap(x, y); x[sa[1]] = 1, num = 1;
			for (int i = 2; i <= n; i++)
				x[sa[i]] = (y[sa[i - 1]] == y[sa[i]] && y[sa[i - 1] + k] == y[sa[i] + k]) ? num : ++num;
			if (num == n) break; m = num;
		}
		for (int i = 1; i <= n; i++) rnk[sa[i]] = i;
	}
	void query() {
		for (int l = 1, r = n / 2, tot = 1; l <= r; tot++) {
			int x = rnk[l], y = rnk[n - r + 1];
			if (x < y) cout << (char)(st[l] + 'A' - 1), l++;
				else cout << (char)(st[r] + 'A' - 1), r--;
			if (tot % 80 == 0) cout << "\n";
		}
	}
} SA;
int main() {
	ios::sync_with_stdio(false);
	cin >> n;
	for (int i = 1; i <= n; i++) {
		char c; cin >> c;
		st[i] = c - 'A' + 1;
	}
	for (int i = 1; i <= n; i++) st[i + n] = st[n - i + 1]; n *= 2;
	SA.build(); SA.query();
	return 0;
}

posted @ 2018-08-14 22:22  谜のNOIP  阅读(113)  评论(0编辑  收藏  举报