1403 最長可重疊子串問題 二分+暴力
阿新 • • 發佈:2019-01-02
題意:
小Hi想知道一段旋律中出現次數至少為K次的旋律最長是多少?
思路:
這個題目應該是字尾陣列的幾個經典之一.
首先要求重疊的最長,那麼我們可以知道我們對所有的字尾排個序的話,字典序越接近的他的字首越長,這是很顯然的.也就是我們的height陣列.
接著我們就可以想到求n個字尾的最長公共字首:
①首先將N個字串按照字典序排列。
②計算height(2), height(3)... height(n),得到height序列。
③最後,height序列中的最小元素的值,就是N個字串的最長公共字首的長度。
所以我們可以想到,我要求至少重複k次的最長公共字首,那麼我只需要找到height陣列中,連續的k-1個的最小值,這代表的就是出現了k次的每個公共字首的數量,然後維護一下這個的最大值就是最終的結果了.
PS: 我們只需要維護出現k次就好了,可以想一下題目要求至少為k次,我們按照字典序排序後,k+1個字元的最長公共字首是小於等於k個字元的最長公共字首的.
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define inf 0x3f3f3f3f using namespace std; const int maxn = 1e5+5; int t1[maxn], t2[maxn], c[maxn]; //c 基數排序輔助陣列 int a[maxn]; int ra[maxn], height[maxn]; //rank陣列,高度陣列 int sa[maxn]; //suffix array int 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 str[], int sa[], int ra[], int height[], 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]=str[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; } int k = 0; n--; for(i = 0; i <= n; i++) ra[sa[i]] = i; for(i = 0; i < n; i++) { if(k) k--; j = sa[ra[i]-1]; while(str[i+k]==str[j+k]) k++; height[ra[i]] = k; } } int main() { // freopen("a.in","r",stdin); int n,K; while(cin>>n>>K) { for(int i=0;i<n;i++) scanf("%d",&a[i]); da(a,sa,ra,height,n,127); if(K==0) { printf("%d\n",n); continue; } int ans=0; for(int i=1;i<=n-K+1;i++) { int mm=inf; for(int j=0;j<K-1;j++) { if(mm>height[i+j]) mm=height[i+j]; } if(mm>ans) ans=mm; } printf("%d\n",ans); } }
這個題還有另外一種做法就是二分.類似於最小值最大化問題,所以我們可以想到二分這個旋律的長度然後帶回驗證.
道理是一樣的,將連續的height陣列劃分集合,舍掉所有LCP小於mid的,然後重新劃分.這樣就保證了得到的每一個集合中的LCP都說>=mid,如果有一個集合中的字串數量>=k就滿足.
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int maxn = 1e5+5; int t1[maxn], t2[maxn], c[maxn]; int ra[maxn], height[maxn]; int sa[maxn], num[maxn]; bool cmp(int *r, int a, int b, int l) { return r[a]==r[b]&&r[a+l]==r[b+l]; } void da(int str[], int sa[], int ra[], int height[], 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]=str[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; } int k = 0; n--; for(i = 0; i <= n; i++) ra[sa[i]] = i; for(i = 0; i < n; i++) { if(k) k--; j = sa[ra[i]-1]; while(str[i+k]==str[j+k]) k++; height[ra[i]] = k; } } int n, k; bool judge(int x) { int tmp = 1; for(int i = 1; i < n; ) { int cnt = 1; int j = i+1; while(height[j] >= x && j <= n) j++, cnt++; tmp = max(tmp, cnt); i = j; } return tmp >= k; } int main(void) { while(cin >> n >> k) { for(int i = 0; i < n; i++) scanf("%d", &num[i]); da(num, sa, ra, height, n, 127); int l = 0, r = n, ans = 0; while(l <= r) { int mid = (l+r)/2; if(judge(mid)) ans = mid, l = mid+1; else r = mid-1; } printf("%d\n", ans); } return 0; }