poj 3415 后缀数组+单调栈||后缀自动机

Common Substrings
Time Limit: 5000MS Memory Limit: 65536K
Total Submissions: 11519 Accepted: 3814
Description

A substring of a string T is defined as:

T(i, k)=TiTi+1…Ti+k-1, 1≤i≤i+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) | k≥K, 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 ≤ 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
问两个串大于等于k的长度的公共子串个数
后缀数组拼起来,
一个很明显的想法是一个块区间的首和尾的最大lcp是之间的最小值,所以对于每个属于B串的子串,我们要找出前面所有的A串和它的公共子串且大于等于k的个数,这样每个串的贡献应该是每个A串和B串和这个串公共子串,lcp-k+1,如何求每个A串和当前B串的lcp呢,暴力的话是on^2,不可行,由于开始我们把大于k的都放在一个块里面,所以在一个块里面,必然每个A都会对下面的B有贡献的,很明显我们是可以o1算出每个A串的贡献的 sum+=height数组-k+1,但是这是算到最大的贡献,很有可能当你遇到这个B的时候这两个公共lcp 比height数组小很多,所以使用单调栈,因为两者的lcp只会越来越小,把这些A串的height存在栈里面,如果这个height比当前的大,那么证明这个贡献必须减少,所以

        while(tail>0&&st[tail]>height[i])
        {
            sum-=(1ll*st[tail]-k+1)*cnt[tail];
            sum+=(1ll*height[i]-k+1)*cnt[tail];
            num+=cnt[tail];
            tail--;
        }

我们同时用cnt数组记录这个串承载了多少了个串的本事,因为他们的贡献将变一样,所以就可以完全弹走了,而算A串的时候,B串没有初始化为num=1,所以必然不会有B串的贡献,很很很巧妙

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

const int MAXN = 2e5+500;  
int t1[MAXN],t2[MAXN],c[MAXN];  
int len1,len2;

bool cmp(int *r,int a,int b,int l)  
{  
    return r[a]==r[b]&&r[a+l]==r[b+l];  
}  

void da(char  str[],int sa[],int ra[],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++) ra[sa[i]]=i;  
    for(i=0;i<n;i++)   
    {  
        if(k) k--;  
        j=sa[ra[i]-1];  
        while(str[i+k]==str[j+k])k++;  
        height[ra[i]]=k;  
    }  
}  

int ra[MAXN],height[MAXN];  
int sa[MAXN],num[MAXN];  
char str[MAXN];
int st[MAXN];
typedef long long ll;
ll cnt[MAXN];

ll solve(int k,int n)
{
    int tail=0;
    ll sum=0,ans=0;
    for(int i=1;i<=n;i++)
    {
        if(height[i]<k) 
        {
            sum=0;
            tail=0;
            continue;
        }
        ll num=0;
        while(tail>0&&st[tail]>height[i])
        {
            sum-=(1ll*st[tail]-k+1)*cnt[tail];
            sum+=(1ll*height[i]-k+1)*cnt[tail];
            num+=cnt[tail];
            tail--;
        }
        st[++tail]=height[i];
        if(sa[i-1]<len1)
        {
            sum+=(1ll*height[i]-k+1);
            cnt[tail]=num+1;
        }
        else cnt[tail]=num;
        if(sa[i]>len1) ans+=sum;
    }
    tail=0;
    sum=0;
    for(int i=1;i<=n;i++)
    {
        if(height[i]<k) 
        {
            sum=0;
            tail=0;
            continue;
        }
        ll num=0;
        while(tail>0&&st[tail]>height[i])
        {
            sum-=(1ll*st[tail]-k+1)*cnt[tail];
            sum+=(1ll*height[i]-k+1)*cnt[tail];
            num+=cnt[tail];
            tail--;
        }
        st[++tail]=height[i];
        if(sa[i-1]>len1)
        {
            sum+=(1ll*height[i]-k+1);
            cnt[tail]=num+1;
        }
        else cnt[tail]=num;
        if(sa[i]<len1)
            ans+=sum;
    }
    return ans;
}

int main()
{
        int k;
        while(~scanf("%d",&k))
        {
            if(!k) break;
            scanf("%s",str);
            len1=strlen(str);
            str[len1]='#';
            scanf("%s",str+len1+1);

            len2=strlen(str);
            da(str,sa,ra,height,len2,130);
            printf("%lld\n",solve(k,len2) );
        }
}

Max 数组 能接受的字符串的最长长度
Min 数组 能接受的字符串的最小长度 和Max[fail[p]] 一样
right 能接受的字符串在原串中出现位置的集合
sz right的大小
Max-Min+1 就是每个状态的子串的数目

后缀自动机的做法就是,利用寻找最长公共子串这个匹配,遇到这个点,如果最大匹配长度大于等于k,那么
更新的是 ans+=Max[p]-max(Max[fail[i]]+1,k)+1)就好,然后更新父节点,首先匹配最长公共子串的时候能到这个点证明长度必然 >=Min[p] 那么父节点必然也可以更新,比较k和Min[p] 如果k大于等于Min[p],那么父节点必然没贡献,不然就是有贡献 标记一下,最后拓扑更新就行

#include <bits/stdc++.h>
using namespace std;

const int maxn = 200000+5;
int last,tail,in[maxn],Min[maxn];
int Max[maxn],cnt[maxn],vis[maxn];
int nxt[maxn][26],fail[maxn];

char sa[maxn],sb[maxn];
typedef long long ll;
ll sz[maxn];
int Maxi[maxn];
inline void build(char *s)
{
    while(*s)
    {
        int p=last,t=++tail,c=*s++-'a';
        Max[t]=Max[p]+1;
        sz[t]=1;
        Maxi[t]=0;
        while(p&&!nxt[p][c])
            nxt[p][c]=t,p=fail[p];
        if(p)
        {
            int q=nxt[p][c];
            if(Max[q]==Max[p]+1)
                fail[t]=q,Min[t]=Max[q]+1;
            else
            {
                int k=++tail;
                Maxi[k]=0;
                fail[k]=fail[q];
                fail[t]=fail[q]=k;
                Max[k]=Max[p]+1;
                memcpy(nxt[k],nxt[q],sizeof(nxt[q]));
                while(p&&nxt[p][c]==q)
                    nxt[p][c]=k,p=fail[p];
            }
        }
        else
            fail[t]=Min[t]=1;
        last=t;
    }
}


int b[maxn];
ll flag[maxn];

int main()
{
    int k;
    while(~scanf("%d",&k))
    {
        if(!k) break;
        int n;
        memset(nxt,0,sizeof(nxt));
        memset(b,0,sizeof(b));
        memset(cnt,0,sizeof(cnt));
        memset(flag,0,sizeof(flag));
        scanf(" %s",sa);
        last=1,tail=1;
        build(sa);
        int hh=strlen(sa);
        for(int i=1;i<=tail;i++) cnt[Max[i]]++;
        for(int i=1;i<=hh;i++) cnt[i]+=cnt[i-1];
        for(int i=1;i<=tail;i++) b[cnt[Max[i]]--]=i;
        for(int i=tail;i>=1;i--) sz[fail[b[i]]]+=sz[b[i]];
        ll ans=0;
        scanf(" %s",sb);
        n=strlen(sb);
        int p=1;
        int len=0;
        for(int i=0;i<n;i++)
        {
            int c=sb[i]-'a';
            if(nxt[p][c]) len++,p=nxt[p][c];
            else
            {
                while(p&&!nxt[p][c])
                    p=fail[p];
                if(!p) p=1,len=0;
                else {
                    len=Max[p]+1;
                    p=nxt[p][c];
                }
            }

//            Maxi[p]=max(Maxi[p],len);
//            if(len>=k) flag[p]++;
              if(len>=k)
              {
                  ans+=1ll*(len-max(k,Max[fail[p]]+1)+1)*sz[p];
                  if(k<=Max[fail[p]]) flag[fail[p]]++;
              }
        }

        for(int i=tail;i>=1;i--)
        {
            int p=b[i];
            ans+=max(0ll,1ll*flag[p]*(Max[p]-max(Max[fail[p]]+1,k)+1)*sz[p]);
            if(k<=Max[fail[p]])
                    flag[fail[p]]+=flag[p];
        }
        printf("%lld\n",ans );
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值