Loading

POJ 1743:Musical Theme(后缀数组+二分)

题目链接

题意

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

  1. 长度至少为5个音符。
  2. 在乐曲中重复出现。(可能经过转调,“转调”的意思是主题序列中每个音符都被加上或减去了同一个整数值)
  3. 重复出现的同一主题不能有公共部分。

思路

论文里面出现的题目。

因为加上和减去同一个整数值,就可以作差,因为减出来可能是个负数,因此需要+88.

二分答案,转化为是否存在两个长度为k的子串是相同的,且不重叠。

将height数组根据k分组,使得每组的后缀之间的height值都大于等于k,有希望成为最长公共前缀不小于k的两个后缀在同一组。

要使得两个串不重叠,只要判断同一组里面最大的sa值和最小的sa值的差大于等于k,这样就可以不重叠了。

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
using namespace std;
typedef long long LL;
typedef pair<int, int> pii;
const int INF = 0x3f3f3f3f;
const int N = 2e5 + 11;
int t1[N], t2[N], c[N], s[N], str[N], Hash[N], sa[N], rak[N], heigth[N], n;

bool cmp(int *r, int a, int b, int l) {
	return r[a] == r[b] && r[a+l] == r[b+l];
}

void DA(int s[], int n, int m) {
	n++;
	int i, j, p, *x = t1, *y = t2;
	for(i = 0; i < m; i++) c[i] = 0;
	for(i = 0; i < n; i++) c[x[i] = s[i]]++;
	for(i = 1; i < m; i++) c[i] += c[i-1];
	for(i = n - 1; i >= 0; i--) sa[--c[x[i]]] = i;
	for(j = 1; j < n; j <<= 1) {
		p = 0;
		for(i = n - j; i < n; i++) y[p++] = i;
		for(i = 0; i < n; i++) if(sa[i] >= j) y[p++] = sa[i] - j;

		for(i = 0; i < m; i++) c[i] = 0;
		for(i = 0; i < n; i++) c[x[y[i]]]++;
		for(i = 1; i < m; i++) c[i] += c[i-1];
		for(i = n - 1; i >= 0; i--) sa[--c[x[y[i]]]] = y[i];

		swap(x, y);
		p = 1; x[sa[0]] = 0;
		for(i = 1; i < n; i++)
			x[sa[i]] = cmp(y, sa[i-1], sa[i], j) ? p - 1 : p++;
		if(p >= n) break;
		m = p;
	}
	n--;
	int k = 0;
	for(i = 0; i <= n; i++) rak[sa[i]] = i;
	for(i = 0; i < n; i++) {
		if(k) k--;
		j = sa[rak[i]-1];
		while(s[i+k] == s[j+k]) k++;
		heigth[rak[i]] = k;
	}
}

bool check(int k) {
	int mi = INF, ma = -INF;
    for(int i = 2; i <= n; i++) {
		if(heigth[i] >= k) {
			mi = min(mi, min(sa[i], sa[i-1]));
			ma = max(ma, max(sa[i], sa[i-1]));
			if(ma - mi >= k) return 1;
		} else {
			mi = INF, ma = -INF;
		}
    } return 0;
}

int main() {
	while(scanf("%d", &n), n) {
		for(int i = 0; i < n; i++) scanf("%d", &s[i]);
		for(int i = 0; i < n; i++) s[i] = s[i+1] - s[i] + 88;
		int m = 177;
		n--;
		s[n] = 0;
        DA(s, n, m);
        int l = 0, r = n, ans = l;
        while(l <= r) {
			int mid = (l + r) >> 1;
            if(check(mid)) l = mid + 1, ans = mid;
            else r = mid - 1;
        }
        if(ans < 4) puts("0");
        else printf("%d\n", ans + 1);
	}
	return 0;
}
posted @ 2017-11-02 16:05  Shadowdsp  阅读(156)  评论(0编辑  收藏  举报