POJ_3415 Common Substrings 后缀数组

题目链接:http://poj.org/problem?id=3415

题意:给你两个字符串str0、str1和K,求一共有多少个这样的三元组S{i,j,k}= {(i , j , k) | str0{i...i+k-1} == str1{j..j+k-1} }。

思路:题意就是在str0中找一个i,在str1中找一个j,要求他们的最长公共前缀l >= K,这样这组i,j对答案的贡献值就是l - K + 1,最后我们要求的就是所有这些之和。因为要求最长公共前缀,所以我们想到用后缀数组。然后对height数组进行分组,所有>=K的分成一组。至此可以肯定的是:所有满足条件的i,j一定位于同一分组内,所以我们可以单独处理每一组。对于每一组,我们现在的任务就是找一个在str0中的后缀,然后再找一个在str1中的后缀,然后求出其lcp,它对答案的贡献值就是lcp-K+1。我们可以这样来考虑:对于一个在str0中的后缀i,我们在它之前找到所有在str1中的后缀,然后在它之后找到所有在str1中的后缀,然后分别处理。下面的分析过程只考虑向前找,但是这里会出现一个问题,那就是如果用O(N)的时间去找的话,整个复杂度就是O(N^2)显然会超时,我们需要想一个优化。仔细分析每一个在i之前,同时还在str1中的后缀j,它与i组成的(i , j)对答案的贡献值是lcp(i , j)-K+1,其中lcp(i ,j ) = min{ height[k] | i+1<=k<=j },这里我们可以发现一个单调性:lcp(i ,j)随着j的增大不减。 这个性质是显然的,这样也就是说我们可以维护一个height值单调递增的队列,假设队列中现在有以下的值:a[0] < a[1] < a[2] < a[3] ,此时i之前的所有在str1中的j后缀和i组成的贡献值就是:a[3]*lcp为a[3]的str1中j的个数(其实这里的个数就是( a[2] , a[3] ]区间内str1中的j的个数 )+ a[2] * ..... ,为了方便,我们可以用一个b数组来记录上面的个数。 于是答案就变成了 a[0]*b[0] + a[1]*b[1] + ..... 但是还有一个问题:这样每次还是要遍历整个队列,最坏情况下整个过程的复杂度还是O(N^2),所以还是会超时。下面还有一个优化,那就是用一个变量保存这个和,每次更新队列的时候同时更新这样变量的值,具体就是代码中的ss。至此,本题完美解决。

代码:

#include <stdio.h>
#include <string.h>
typedef long long LL  ;
int K,N;
const int NN = 100010 * 2 ;
char r[ NN ] ;
char str[2][NN] ;
int loc[ NN ] , pos[NN];
int len1, len2 ;
int sa[NN] , wa[NN] , wb[NN] , ws[NN] ,wv[NN] ;
int rank[NN] , height[NN] ;
int cmp(int *r,int a, int b ,int l)
{return r[a]==r[b] && r[a+l]==r[b+l] ;}
void da(char *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++ ;
    }
}
void calc_height(char *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++) ;
    }
}
int f[NN] , a[NN] , b[NN] ;

void solve(){
    int c = 0 ;
    LL ss = 0 ;
    int i , j , k , t ;
    for(i=0;i<=N;i++)   f[i] = sa[i] < len1 ;
    LL ans = 0 ;
    for(i=2;i<=N;i=j+1){
        for( ;height[i]<K && i<=N;i++) ;
        for(j=i ; height[j]>=K && j<=N ;j++ ) ;
        for(t = 0 ; t < 2; t++ ){
            c = 0 ; ss = 0 ;
            for(k=i-1;k<j;k++){
                if( f[k] == t ) ans += ss ;
                c++ ;
                a[c] = height[k+1] - K + 1 ;
                b[c] = f[k]!=t ;
                ss += (LL)a[c]*b[c] ;
                while( c>=2 && a[c-1]>=a[c] ){
                    ss -= (LL)( a[c-1] - a[c] ) * b[c-1]  ;
                    a[c-1] = a[c] ;
                    b[c-1] += b[c] ;
                    c-- ;
                }
            }
        }
    }
    printf("%lld\n",ans);
}
int main(){
    while( scanf("%d",&K) && K ){
        scanf("%s %s",str[0] , str[1]);
        len1 = strlen( str[0] ) ; len2 = strlen( str[1] ) ;
        N = 0 ;
        for(int i=0;i<len1;i++) r[N + i] = str[0][i]  , loc[N + i] = 1 , pos[N + i] = i ;
        r[N + len1] = 1 ;  loc[N + len1] = 0 ;  pos[N + len1] = len1 ;
        N += len1 + 1 ;
        for(int i=0;i<len2;i++) r[N + i] = str[1][i] , loc[N + i] = 2 , pos[N + i] = i ;
        r[N + len2] = 0 ;   loc[N + len2] = 0 ; pos[N + len2] = len2 ;
        N += len2  ;
        da(r  ,sa , N+1 , 200 ) ;
        calc_height( r , sa , N ) ;
        solve() ;
    }
    return 0;
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值