POJ - 3415 Common Substrings(后缀数组 + 单调栈)

题意:

求两个子串的大于等于k 的重复子串有多少个。

{

求两个子串的重复子串我们可以用单调栈来做,

然而,在同一个子串中,我们想要找重复子串的个数,那就是 height数组之和。

}

思路:

两个子串,我们用后缀数组维护,把两个子串合并在一起,然后中间加一个特殊字符。

{  看网上别人的博客。

一个n长度的子串,我们把后边填一个0,就是在位置n上加0, 这个字符串我们从 0 编号。

然后我们求后缀数组的时候,n + 1.

再求 height 的时候 n 就不用加1了。

然后,, rak 的下标 从 0 到 n-1 值是从1 到n

     sa 的下标是 从 1 到 n ,,值是从0 到 n-1

   height 的下标是从 2 到 n;

}

这个题。我们利用后缀数组的height数组。来计算两个子串的公共长度。

我们来看一下样例一。 用后缀数组处理完止之后,排序之后的子串。

// 尴尬,height 多写了e

我们来看看这个图片,有什么有意思的地方。

我们枚举 1 到 n,我们知道当前位置的height,然后找左右的height 的值不小于当前的height值。

在这个区间内,找找有多少子串属于第一个串,多少子串属于第二个串,然后属于两个子串的个数相乘就好了,然后把所有相乘的结果加起来。 

找到的区间就是,这个区间里的所有子串都可以匹配 height 个字母。

找左右区间的时候,我们就要用到单调栈了,把L,R处理出来。

我们怎么处理在一个区间里有多少个子串属于第一个串,有多少个子串属于第二个串呢?

我们有 sa数组,它记录排名为i的子串的位置,。

我们只要开两个数组,如果第 i 个子串属于 第一个串,就 a[i]  + 1, 否自, b[i] + 1, 然后前缀和,就可以知道一段区间内分别属于两个串的个数了。

这样看起来仿佛是对的, but

我们算重复了。

比如,上面的第 7 行,第 8 行,他们的height值都是2,都大于K,然后他们找到的区间又是一样的,这样就会算重复。

这时候我们新开一个vis数组,记录当前的 heigh 值,所包含的右区间到哪, 当我们再次找到这个height 值的时候,我们看看他的左区间是不是大于vis 数组,如果大于,说明这两个区间不包含,可以计算。反之就说明这两个区间是包含的,不能再计算了。

我们解决了区间包含问题,这时似乎是对的了,

但是,还是不对。

上边图片的 11 行。 他的height 值是4,包含的区间有三个子串可以匹配4个字母,

13 行,值是 3,包含的区间有 四 个子串可以匹配 三个字母,

这时候我们发现,他没有计算有多少子串可以匹配 两个字母 的。

所以我们要在开一个hh数组,记录当前位置height管理的区间,可以匹配多少种 不同的字母个数。。

比如 13 行,可以既可以匹配 3 个字母,,有可以匹配2 个字母。hh值就是2;

比如11 行,只可以匹配4 个字母的情况,hh值就是1.

这个hh值怎么算呢?

就是当前height的值,减去边界的height的值,取一个最小的就好了。

要注意一点, 边界的height的值不能小于k ,所以我们可以提前我们当前位置的  hh 值设为 height - k + 1.

之后再找最小就可以了。

 

 

 

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N=200000+100;
char s[N],s2[N];
int sa[N],t[N],t2[N],c[N];
int height[N],rk[N];
int a[N],b[N],st[N],L[N],R[N];
int vis[N],hh[N];
// vis 记录当前高度能持续到哪里
// hh 记录当前高度和次高的相差多少。
// a 记录一段区间有多少个子串是属于a串
// b 记录一段区间有多少个子串是属于b串
// st 单调栈。
// L、R 单调栈的左右区间。
void SuffixSort(int n,int m)
{
    int i,*x=t,*y=t2;
    for(i=0;i<m;++i) c[i]=0;
    for(i=0;i<n;++i) x[i]=s[i];
    for(i=0;i<n;++i) c[x[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(int k=1;k<=n;k<<=1)
    {
        int p=0;
        for(i=n-k;i<n;++i) y[p++]=i;
        for(i=0;i<n;++i) if(sa[i]>=k) y[p++]=sa[i]-k;
        for(i=0;i<m;++i) c[i]=0;
        for(i=0;i<n;++i) c[x[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]]=(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k])?p-1:p++;
        if(p>=n) break;
        m=p;
    }
}
void getHeight(int n)
{
    int i,j,k=0;
    for(i=1;i<=n;++i) rk[sa[i]]=i;
    for(i=0;i<n;++i)
    {
        if(k) k--;
        j=sa[rk[i]-1];
        while(s[i+k]==s[j+k]) k++;
        height[rk[i]]=k;
    }
}
void slove(int n, int n1,int k){
    memset(a,0,sizeof(a));
    memset(b,0,sizeof(b));
    int t;
    for(int i = 1; i <= n; i++){
        if (sa[i] < n1) a[i] = a[i-1]+1; else a[i] = a[i-1];
        if (sa[i] > n1) b[i] = b[i-1]+1; else b[i] = b[i-1];
    }
    t = 1;
    for(int i = 1; i <= n; i++){
        while(t > 1 && height[st[t-1]] >= height[i])t--;
        L[i] = (t == 1) ? 1 : (st[t-1]);
        st[t++] = i;
        hh[i] = min(height[i] - height[L[i]],height[i]-k+1);
    }
    t = 1;
    for (int i = n; i >= 1; i--){
        while(t > 1 && height[st[t-1]] >= height[i])t--;
        R[i] = (t == 1)?n:st[t-1]-1;
        st[t++] = i;
        if (R[i] != n) hh[i] = min(hh[i],height[i] - height[R[i]+1]);
    }
}
long long  the_end(int n, int k){
    memset(vis,0,sizeof(vis));
    long long ans = 0;
    for (int i = 1; i <= n; i++){
        if (height[i] >= k && L[i] > vis[height[i]]){
            vis[height[i]] = R[i];
            // printf("%d %d %d %d\n",i+2,a[R[i]] - a[L[i]-1],b[R[i]] - b[L[i]-1],hh[i]);
            ans += (long long)(a[R[i]] - a[L[i]-1])*(b[R[i]] - b[L[i]-1])*hh[i];
        }
    }
    return ans;
}
int main()
{
    int k;
    while(~scanf("%d",&k) && k){
    int n1,n;
    scanf("%s",s);
    n1=strlen(s);
    s[n1]='$';
    scanf("%s",s+n1+1);
    n=strlen(s);
    SuffixSort(n+1,150); //字符后面补一个0,所以n+1.
    getHeight(n); //算这个的时候不用加0.
    slove(n,n1,k);
    long long ans = the_end(n,k);
    printf("%I64d\n",ans);
    }
    return 0;
}
 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值