POJ 1743 后缀数组
题目链接:http://poj.org/problem?id=1743
题意:给定一个钢琴的音普序列[值的范围是(1~88)],现在要求找到一个子序列满足
1,长度至少为5
2,序列可以转调,即存在两个子序列,满足一个子序列加/减一个数后可以得到另一个序列
3,两个序列不能有相交的部分。
题意简单来说就是找最长不重叠的重复子串
思路:直接根据09年oi论文<<后缀数组——出来字符串的有力工具>>的解法,先二分答案,把题目变成判定性问题:判断是否存在两个长度为k 的子串是相同的,且不重叠。解决这个问题的关键还是利用height 数组。把排序后的后缀分成若干组,其中每组的后缀之间的height 值都不小于k。容易得出,有希望成为最长公共前缀不小于k 的两个后缀一定在同一组。然后对于每组后缀,只须判断每个后缀的sa 值的最大值和最小值之差是否不小于k。如果有一组满足,则说明存在,否则不存在。整个做法的时间复杂度为O(nlogn)。
对于第二点要求的转换,即转调条件,一个子序列加/减一个数后可以得到另一个序列实际是两个序列的变化程度是一样的,比如序列1 2 3 4 5 6 7 8 9 10 那么对于这个序列的可以得到长度为5的两个不相交的"转调后的重复子序列", 序列一:1 2 3 4 5(变化程度 1 1 1 1) 序列二:6 7 8 9 10(变化程度1 1 1 1) ,变化程度为相邻两个数字的差值,得到变化序列后我们就可以用上面的思路来求解了。 因为是通过变化序列来求解,所以答案序列是变化序列+1。
#define _CRT_SECURE_NO_DEPRECATE #include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<string> #include<queue> #include<vector> #include<time.h> #include<cmath> using namespace std; typedef long long int LL; const int MAXN = 300000 + 5; int wa[MAXN], wb[MAXN], wv[MAXN], WS[MAXN]; int cmp(int *r, int a, int b, int l){ return r[a] == r[b] && r[a + l] == r[b + l]; } void da(int *r, int *sa, int n, int m){ int i, j, p, *x = wa, *y = wb, *t; for (i = 0; i<m; i++) WS[i] = 0; for (i = 0; i<n; i++) WS[x[i] = r[i]]++; for (i = 1; i<m; i++) WS[i] += WS[i - 1]; for (i = n - 1; i >= 0; i--) sa[--WS[x[i]]] = i; for (j = 1, p = 1; p<n; j *= 2, m = p) { for (p = 0, 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<n; i++) wv[i] = x[y[i]]; for (i = 0; i<m; i++) WS[i] = 0; for (i = 0; i<n; i++) WS[wv[i]]++; for (i = 1; i<m; i++) WS[i] += WS[i - 1]; for (i = n - 1; i >= 0; i--) sa[--WS[wv[i]]] = y[i]; for (t = x, x = y, y = t, p = 1, x[sa[0]] = 0, i = 1; i<n; i++) x[sa[i]] = cmp(y, sa[i - 1], sa[i], j) ? p - 1 : p++; } return; } int Rank[MAXN], height[MAXN],sa[MAXN]; void calheight(int *r, int *sa, int n){ int i, j, k = 0; for (i = 1; i <= n; i++) Rank[sa[i]] = i; for (i = 0; i<n; height[Rank[i++]] = k) for (k ? k-- : 0, j = sa[Rank[i] - 1]; r[i + k] == r[j + k]; k++); return; } int key[MAXN],n; bool check(int k){ int tot = 0,maxsa=sa[0],minsa=sa[0]; for (int i = 1; i < n; i++){ if (height[i] >= k){ maxsa = max(maxsa, sa[i]); minsa = min(minsa, sa[i]); } else{ if (maxsa - minsa >= k){ return true; } maxsa = sa[i], minsa = sa[i]; } } if (maxsa - minsa >= k){ return true; } return false; } int main(){ //#ifdef kirito // freopen("in.txt","r",stdin); // freopen("out.txt","w",stdout); //#endif // int start = clock(); while (scanf("%d", &n)&&n!=0){ for (int i = 0; i < n; i++){ scanf("%d", &key[i]); } for (int i = 0; i < n; i++){ key[i] = key[i + 1] - key[i] + 100; //+100的原因:因为key[i]为[1~88]所以相减可能出现负数 //但是负数不会超过-88所以+100把他转换成正数。 if (i == n - 1){ key[i] = 0; } //最后一个key[i]后面已经没元素了,所以把他置为0,相当于 //后缀数组经常在字符串最后添加一个比所有字符都要小的值 //因为变化的key[i]最小为-88,然后+100所以最小值一定比0大 //相当于0是添加进去的最小字符。 } da(key, sa, n, 200); //字符数组,sa数组,元素个数,元素的最大值(200>88+100) calheight(key, sa, n-1); int l = 0, r = n, mid; //二分求解,找满足条件的最大值 while (r >= l){ mid = (l + r) / 2; if (check(mid)){ l = mid + 1; } else{ r = mid - 1; } } if (r < 4){ //不满足条件1 printf("0\n"); } else{//实际长度为编号后的长度+1 printf("%d\n", r + 1); } } //#ifdef LOCAL_TIME // cout << "[Finished in " << clock() - start << " ms]" << endl; //#endif return 0; }