poj 3415 后缀数组+单调队列

来源:互联网 发布:数据恢复最好的软件 编辑:程序博客网 时间:2024/05/29 12:50

Common Substrings
Time Limit: 5000MS Memory Limit: 65536KTotal 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 defineS, 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 andK.

Input

The input file contains several blocks of data. For each block, the first line contains one integerK, followed by two lines containing strings A and B, respectively. The input file is ended byK=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

2aababaaabaabaa1xxxx0

Sample Output

225

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







0 0
原创粉丝点击