poj 3415 後綴數組+單調隊列

Common Substrings
Time Limit: 5000MS   Memory Limit: 65536K
Total Submissions: 8106   Accepted: 2688

Description

A substring of a string T is defined as:

T(i, k)=TiTi+1...Ti+k-1, 1≤ii+k-1≤|T|.

Given two strings A, B and one integer K, we define S, a set of triples (i, j, k):

S = {(i, j, k) | kK, A(i, k)=B(j, k)}.

You are to give the value of |S| for specific A, B and K.

Input

The input file contains several blocks of data. For each block, the first line contains one integer K, followed by two lines containing strings A and B, respectively. The input file is ended by K=0.

1 ≤ |A|, |B| ≤ 105
1 ≤ Kmin{|A|, |B|}
Characters of A and B are all Latin letters.

Output

For each case, output an integer |S|.

Sample Input

2
aababaa
abaabaa
1
xx
xx
0

Sample Output

22
5

Source

POJ Monthly--2007.10.06, wintokk


題意是求兩個字符串長度>=K的公共子串有多少個。
先考慮怎麼求解公共子串的問題。公共子串可以用後綴數組求,將兩個字符串str1和str2合併爲一個字符串,中間插入一個不會出現的字符做分割。得到後綴數組和高度數組,有了高度數組就可以求出任意兩個後綴的lcp最長公共前綴值,lcp即爲兩個後綴之間height[i]的最小值。
回到這個問題上,假如兩個分別屬於str1和str2的後綴lcp爲X (X>K), 那麼就計數X-K+1個長度>=K的公共子串。那麼答案就是每個屬於str1和每個屬於str2的lcp-K+1求和。
直接枚舉要n^2logn複雜度(單次查詢兩個後綴lcp用rmq--logn複雜度)。任意兩個後綴的lcp值取決於它們之間的最小值,直接枚舉慢就慢在每兩個後綴都要找一遍它們之間的最小值。利用最小值這個關鍵點,假如枚舉以第i個lcp值爲最小值的區間[l,r],那麼ans+=(左邊屬於str1的後綴個數*右邊屬於str2+左邊屬於str2*右邊屬於str1)*(lcp-K+1)。而找到以某個值爲最小值的區間用單調隊列左右掃一遍O(n)就可以得到。

#include <iostream>
#include <cstring>
#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;

const int maxn=200005;
int t1[maxn], t2[maxn], c[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 rank[], 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++) rank[sa[i]]=i;
    for(i=0; i<n; i++){
        if(k)k--;
        j=sa[rank[i]-1];
        while(str[i+k]==str[j+k]) k++;
        height[rank[i]]=k;
    }
}
int n;
int rank[maxn], height[maxn];
char s[maxn];
int r[maxn];
int sa[maxn];

int que[maxn], head,tail;
int lb[maxn], rb[maxn];

int sum[maxn]={0};
int query(int l, int r)
{
    return sum[r]-sum[l-1];
}

int main()
{
    int K;
    while(scanf("%d", &K)==1 &&K){
        scanf("%s", s);
        int len=strlen(s);
        for(int i=0; i<len; i++) {r[i]=s[i];}
        scanf("%s", s);
        int len1=strlen(s);
        for(int i=0; i<len1; i++) r[len+1+i]=s[i];
        n=len+1+len1;
        r[len]=1;
        r[n]=0;
        da(r,sa,rank, height, n,128);

        head=0,tail=-1;
        for(int i=1; i<=n; i++){
            while(tail+1!=head && height[que[tail]]>height[i]) tail--;
            if(tail+1!=head) lb[i]=que[tail]+1;
            else lb[i]=1;
            que[++tail]=i;
        }
        head=0, tail=-1;
        for(int i=n; i>=1; i--){
            while(tail+1!=head && height[que[tail]]>=height[i]) tail--;
            if(tail+1!=head) rb[i]=que[tail]-1;
            else rb[i]=n;
            que[++tail]=i;
        }

        memset(sum, 0, sizeof(sum)); //前綴和維護區間內屬於str1的後綴個數
        for(int i=1; i<=n; i++){
            if(sa[i]<len)
             sum[i]++;
             sum[i]+=sum[i-1];
        }
        long long ans=0;//注意答案可能大於int
        for(int i=2; i<=n; i++){
            if(height[i]<K) continue;
            int l=lb[i]-1, r=rb[i];
            int ltot=i-l, rtot=r-i+1;
            ans+=(long long)query(l, i-1)*(rtot-query(i, r))*(height[i]-K+1);
            ans+=(long long)(ltot-query(l,i-1))*query(i, r)*(height[i]-K+1);
        }
        printf("%lld\n", ans);
    }
    return 0;
}







發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章