POJ1743 Musical Theme-后缀数组
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;
}