1. 程式人生 > >1403 最長可重疊子串問題 二分+暴力

1403 最長可重疊子串問題 二分+暴力

題目連結

題意:

小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;  
}