首页 > 技术文章 > POJ1743 Musical Theme-后缀数组

cychester 2018-12-04 15:01 原文

Description

题意:有 N(1 <= N <=20000) 个音符的序列来表示一首乐曲,每个音符都是 1..88 范围内的整数,现在要找一个重复的主题。“主题” 是整个音符序列的一个子串,它需要满足如下条件:

1. 长度至少为 5 个音符。

2. 在乐曲中重复出现。(可能经过转调,“转调” 的意思是主题序列中每个音符都被加上或减去了同一个整数值)

3. 重复出现的同一主题不能有公共部分。

Solution

后缀数组经典应用, 求最长不重叠的重复子串

此题是转调相同, 那么拿出差分数组来跑后缀数组。

原数组不重叠, 差分数组两个子串要隔开\(1\)个字符

然后二分长度\(x\), 两个字符串在一段 \(h[i] >=x\)的连续区间内, 且首字母位置相距\(>x\)

最后的原数组重复子串的长度为 \(ans + 1\)

Code

#include<cstdio>
#include<cstring>
#include<algorithm>
#define rd read()
using namespace std;

const int N = 2e4 + 5, inf = 1e9;

int tax[N], tmp[N], Rank[N], SA[N], rk[N], n, m, h[N], a[N]; 

int read() {
	int X = 0, p = 1; char c = getchar();
	for (; c > '9' || c < '0'; c = getchar())
		if (c == '-') p = -1;
	for (; c >= '0' && c <= '9'; c = getchar())
		X = X * 10 + c - '0';
	return X * p;
}

void radix_sort() {
	memset(tax, 0, sizeof(int) * (m + 1));
	for (int i = 1; i <= n; ++i)
		++tax[Rank[i]];
	for (int i = 1; i <= m; ++i)
		tax[i] += tax[i - 1];
	for (int i = n; i; --i)
		SA[tax[Rank[tmp[i]]]--] = tmp[i];
}

int cmp(int x, int y, int w) {
	if (tmp[x] != tmp[y]) return 0;
	if ((x + w > n ? -1 : tmp[x + w]) == (y + w > n ? -1 : tmp[y + w])) return 1;
	return 0;
}

void get_SA() {
	for (int i = 1; i <= n; ++i)
		Rank[i] = a[i], tmp[i] = i;
	m = 233;
	radix_sort();
	for (int num = 1, w = 1; num < n; w <<= 1, m = num) {
		num = 0;
		for (int i = n - w + 1; i <= n; ++i)
			tmp[++num] = i;
		for (int i = 1; i <= n; ++i) if (SA[i] > w)
			tmp[++num] = SA[i] - w;
		radix_sort();
		for (int i = 1; i <= n; ++i)
			tmp[i] = Rank[i];
		num = Rank[SA[1]] = 1;
		for (int i = 2; i <= n; ++i)
			if (cmp(SA[i], SA[i - 1], w)) Rank[SA[i]] = num;
		else Rank[SA[i]] = ++num;
	}
}

void get_h() {
	for (int i = 1; i <= n; ++i)
		rk[SA[i]] = i;
	for (int k = 0, i = 1; i <= n; ++i) {
		if (k) k--;
		int j = SA[rk[i] - 1];
		while (a[k + i] == a[k + j]) k++;
		h[rk[i]] = k;
	}
}

void up(int &A, int B) {
	if (A < B) A = B;
}

void down(int &A, int B) {
	if (A > B) A = B;
}

int check(int x) {
	for (int i = 2, Min = inf, Max = -inf; i <= n; ++i) {
		if (h[i] >= x) {
			down(Min, SA[i]); down(Min, SA[i - 1]);
			up(Max, SA[i]); up(Max, SA[i - 1]);
			if (Max - Min > x) return 1;
		} else {
			Min = inf; Max = -inf;
		}
	}
	return 0;
}

int work() {
	n = rd; 
	if (!n) return 0;
	for (int i = 1; i <= n; ++i)
		a[i] = rd;
	for (int i = 1; i < n; ++i)
		a[i] = a[i + 1] - a[i] + 100;
	n--;
	get_SA();
	get_h();
	int l = 1, r = n, ans = 0;
	for (; l <= r;) {
		int mid = (l + r) >> 1;
		if (check(mid)) ans = mid, l = mid + 1;
		else r = mid - 1;
	}
	if (ans < 4) puts("0");
	else printf("%d\n", ans + 1); 
	return 1;
}

int main()
{
	while (work());
	return 0;
}

推荐阅读