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 
#include 
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=0;i--) sa[ --ws[x[i]] ] = i ;
    for(j=1,p=1;p= j )    y[p++] = sa[i] - j  ;
        for(i=0;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=K && j<=N ;j++ ) ;
        for(t = 0 ; t < 2; t++ ){
            c = 0 ; ss = 0 ;
            for(k=i-1;k=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



你可能感兴趣的:(ACM-杂题)