poj 3415 Common Substrings

Common Substrings
Time Limit: 5000MS Memory Limit: 65536K
Total Submissions: 7291 Accepted: 2417

Description

A substring of a string T is defined as:

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

Given two strings AB and one integer K, we define S, a set of triples (ijk):

S = {( ijk) |  kKA( ik)= B( jk)}.

You are to give the value of |S| for specific AB 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 ≤ K ≤ min{|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

这道题需要对height数组分组后,用单调栈优化。对于LCP=L>K的前缀,对答案的贡献是L-K+1.即长度为K,K+1.....L的公共字串。对于每一组,栈里维护height值递增,这样保证了每个height的贡献量为height[i]-K+1,因为有定理LCP(i,j)=min(height[i+1],...height[j])如果在height[i]之后插入一个height[k]<height[i],那么height[i]就等于废了,他的价值仅为height[k]了,对于之后的j,LCP(i,j)起码要<=height[k],像这种递减的值插入栈前,需要将栈里>=插入值的变成插入值,然后将插入值的宽度增加。因为这当中为了降低复杂度,用了一个sum来维护当前栈里的后缀与将要入栈的后缀的公共字串的个数。因此实际上的{将栈里>=插入值的变成插入值}的操作,体现在sum上,是对sum减去一段height之差*宽度。

补充:因为rank=i的后缀能与其rank前后的后缀都可能产生公共子串,但此算法只维护其前面的,因此排在A串后缀后面的B串后缀,应该用扫描B串后缀来维护,因为B在A后,以B为参考系,A就在前,这就是为什么要分别扫A和B串的原因。

补充完这段,接着开始:比如我们正在扫A串,那么当B串入栈时,sum均会加上L-K+1,而之后当有height值小的要入栈,此时可以是A串的后缀,他虽然不增加sum的值,但它也起到了阻断作用,它是之前栈里height值>=它的值变废了,因为后面的后缀与栈里算LCP值时,起码要<=这个值,由于栈里原先的元素都加了L-K+1,假设现在入栈的height值为T(T<L),那么它对答案的贡献是T-K+1,它使原先栈里大于等于它废成T,贡献量变为T-K+1,相当于减少了L-K+1-(T-K+1)=L-K(高度差),因为之前入栈时已加在sum上,此时就要从sum中减去了,当然不能忘了乘宽度,因为这些height都会合并,他代表的是一系列height相同的元素,是有数目的。

总之,这道题个人感觉方法很棒!我也是理解了好久,可能上面解释的不太到位吧,还需要各位更深入的研究。

代码:

#include<cstdio>
#include<iostream>
#define ll long long
#define Maxn 200010
using namespace std;

int r[Maxn],sa[Maxn],rk[Maxn],height[Maxn];
int wa[Maxn],wb[Maxn],rs[Maxn],wv[Maxn];
int cmp(int *r,int a,int b,int l){
    return r[a]==r[b]&&r[a+l]==r[b+l];
}
void da(int n,int m){
    int i,j,p,*x=wa,*y=wb;
    for(i=0;i<m;i++) rs[i]=0;
    for(i=0;i<n;i++) rs[x[i]=r[i]]++;
    for(i=1;i<m;i++) rs[i]+=rs[i-1];
    for(i=n-1;i>=0;i--) sa[--rs[x[i]]]=i;
    for(j=1,p=1;p<n;j<<=1,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<m;i++) rs[i]=0;
        for(i=0;i<n;i++) rs[wv[i]=x[y[i]]]++;
        for(i=1;i<m;i++) rs[i]+=rs[i-1];
        for(i=n-1;i>=0;i--) sa[--rs[wv[i]]]=y[i];
        swap(x,y);
        for(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 calheight(int n){
    int i,j,k=0;
    for(int i=1;i<n;i++) rk[sa[i]]=i;
    for(int i=1;i<n;height[rk[i++]]=k){
        if(k) k--;
        for(j=sa[rk[i]-1];r[i+k]==r[j+k];k++);
    }
}
char s[Maxn];
int st[Maxn],len[Maxn];
int main()
{
    int n,i,k,la,lb;
    while(scanf("%d",&k),k){
        scanf("%s",s+1);
        for(i=1;s[i];i++)
            r[i]=s[i];
        r[n=i]='#';
        la=n-1;
        scanf("%s",s+1);
        for(i=1;s[i];i++)
            r[++n]=s[i];
        lb=n-la-1;
        r[0]=r[++n]=0;
        da(n,125);
        calheight(n);
        int top;
        ll ans=0,sum;
        for(int i=1;i<n;i++){
            if(height[i]<k) top=-1,sum=0;
            else{
                int width=0;
                while(top!=-1&&st[top]>height[i]){
                    sum-=len[top]*(st[top]-height[i]);
                    width+=len[top--];
                }
                if(sa[i-1]<=la) width+=1,sum+=height[i]-k+1;
                st[++top]=height[i];
                len[top]=width;
                if(sa[i]>la) ans+=sum;
            }
        }
        for(int i=1;i<n;i++){
            if(height[i]<k) top=-1,sum=0;
            else{
                int width=0;
                while(top!=-1&&st[top]>height[i]){
                    sum-=(ll)len[top]*(st[top]-height[i]);
                    width+=len[top--];
                }
                if(sa[i-1]>la) width+=1,sum+=height[i]-k+1;
                st[++top]=height[i];
                len[top]=width;
                if(sa[i]<=la) ans+=sum;
            }
        }
        printf("%lld\n",ans);
    }
	return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值