poj 3415 Common Substrings(后缀数组+单调栈+dp)

题目链接:点击打开链接

博主是看了后缀数组那篇论文后,然后做了这道题练手。

论文原文:基本思路是计算A 的所有后缀和B 的所有后缀之间的最长公共前缀的长度,把最长公共前缀长度不小于k 的部分全部加起来。先将两个字符串连起来,中间用一个没有出现过的字符隔开。按height 值分组后,接下来的工作便是快速的统计每组中后缀之间的最长公共前缀之和。扫描一遍,每遇到一个B 的后缀就统计与前面的A 的后缀能产生多少个长度不小于k 的公共子串,这里A 的后缀需要用一个单调的栈来高效的维护。然后对A 也这样做一次。具体的细节留给读者思考。

然后在网上搜了好几篇解题报告,都只提到了用单调栈来维护,而都没有讲清楚具体思路。感觉看得模模糊糊,很是不详细,花了不少时间在这个题目上,遂写下了这篇博文,希望大家能给大家带来帮助(本人语文水平不太好,轻喷)。

首先对于字符串的两个后缀ijlcp(最长公共子串),长度为rank[i]+1~rank[j]区间内height[]的最小值(不妨设rank[i]<rank[j])。按照height分组后,需要统计每一个新的后缀与前面的后缀带来的新的公共前缀,这里视为贡献度,这里如果是o(n^2)的复杂度的话,明显超时。所以想到统计新的后缀所带来的贡献的时候,能不能用前一个后缀所做的贡献来加快时间。我们用一个单调递增的栈来维护数据。

对于每一个新的后缀,如果heigh值大于栈顶元素:

例如:

(这里Sa[i]表示的字符串是什么不重要,重要的是动态规划思维过程~~~~)

Sa[1]:ab

Sa[2]:abc

Sa[3]:abcd

Sa[4]:abcde

若此时算到Sa[4]这个新的后缀和前面所有后缀(Sa[3],Sa[2],Sa[1])能带来多大贡献,首先对于(Sa[1]Sa[2])来说,因为height[4]>height[3](单调栈栈顶元素),根据lcp的计算方法,所以Sa[3]能和(Sa[1]Sa[2])产生的贡献值就等同于Sa[3]和(Sa[1]Sa[2])所产生的贡献值,Sa[4]能和Sa[3]产生的贡献值为(height[4]-k+1),所以Sa[4]能产生的新的贡献值=(Sa[3]产生的贡献值+height[4]-k+1)

如果height值大于栈顶元素值:

Sa[1]:a

Sa[2]:ab

Sa[3]:abc

Sa[4]:abcd

Sa[5]:abcde

Sa[6]:abcdef

Sa[7]:abc

Sa[8]:ab

若此时计算到Sa[7],height[7]<栈顶元素值height[6],若此时还按照上面的求解方法,明显多算了Sa[5],Sa[6]后面的后缀e,ef,所以这里要减去Stacksize[i]*(Stack[top]-height[i])这个多算的后缀,Stacksize数组的含义马上会说到,它是表示这一段Stack[i]这一段压缩了多少个数。因为height[7]<栈顶值,但我们要维护一个单调递增的栈,又因为lcp是这段区间的最小值,所以后面和它前面的lcp最大长度不会超过height[7],所以以后开始的后缀同这段区间的lcp不会超过height[7],Stacksize[top]表示严格递增的(height[i]<=3&&height[i]>Stack[top-1])区间含有几个数。后面按照这种规律依次递推就能出结果了~~~其实这就是一个统计上的Dp~~~~~

然后来说,这个题目就不难了,先用一个独特的字符连接两个字符串,求出后缀数组后分情况统计,先求出B的后缀有多少个和A的公共前缀,再统计A的后缀有多少个和B的公共前缀~~~~~~结果就出来了~~~~~~~

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
typedef long long llt;
const int maxn=200010;
int wa[maxn],wb[maxn],wsf[maxn],wv[maxn],sa[maxn];
int ranks[maxn],height[maxn],s[maxn];
char s1[maxn],s2[maxn],str[maxn];
int cmp(int *r,int a,int b,int k)
{
    return r[a]==r[b]&&r[a+k]==r[b+k];
}
void da(char *r,int n,int m)
{
    int i,j,p,*x=wa,*y=wb,*t;
    for(i=0;i<m;i++)  wsf[i]=0;
    for(i=0;i<n;i++)  wsf[x[i]=r[i]]++;
    for(i=1;i<m;i++)  wsf[i]+=wsf[i-1];
    for(i=n-1;i>=0;i--)  sa[--wsf[x[i]]]=i;
    p=1;
    j=1;
    for(;p<n;j*=2,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<n;i++)  wv[i]=x[y[i]];
        for(i=0;i<m;i++)  wsf[i]=0;
        for(i=0;i<n;i++)  wsf[wv[i]]++;
        for(i=1;i<m;i++)  wsf[i]+=wsf[i-1];
        for(i=n-1;i>=0;i--)  sa[--wsf[wv[i]]]=y[i];
        t=x;
        x=y;
        y=t;
        x[sa[0]]=0;
        for(p=1,i=1;i<n;i++)
        x[sa[i]]=cmp(y,sa[i-1],sa[i],j)? p-1:p++;
    }
}
void calheight(char *r,int n)
{
    int i,j,k=0;
    for(i=0;i<n;i++)  ranks[sa[i]]=i;
    for(i=0;i<n-1;i++)
    {
        if(k) k--;
        j=sa[ranks[i]-1];
        while(r[i+k]==r[j+k]) k++;
        height[ranks[i]]=k;
    }
}
int Stack[maxn],Stacksize[maxn];
llt tot,top;
int main()
{
    int k;
    while(scanf("%d%s%s",&k,s1,s2)&&k)
    {
        int L1=strlen(s1),L2=strlen(s2);
        str[0]='\0';
        strcat(str,s1);
        str[L1]=1;
        str[L1+1]='\0';
        strcat(str,s2);
        int n=L1+L2+1;
        da(str,n+1,128);
        calheight(str,n+1);
        tot=top=0;
        llt sum=0;
        for(int i=2; i<=n; i++)
        {
            if(height[i]<k) top=tot=0;
            else
            {
                int cnt=0;
                if(sa[i-1]<L1) cnt++,tot+=height[i]-k+1;
                while(top>0&&height[i]<=Stack[top-1])
                {
                    top--;
                    tot-=Stacksize[top]*(Stack[top]-height[i]);
                    cnt+=Stacksize[top];
                }
                Stack[top]=height[i];
                Stacksize[top++]=cnt;
                if(sa[i]>L1) sum+=tot;
            }
        }
        tot=top=0;
        for(int i=2; i<=n; i++)
        {
            if(height[i]<k) top=tot=0;
            else
            {
                int cnt=0;
                if(sa[i-1]>L1) cnt++,tot+=height[i]-k+1;
                while(top>0&&height[i]<=Stack[top-1])
                {
                    top--;
                    tot-=Stacksize[top]*(Stack[top]-height[i]);
                    cnt+=Stacksize[top];
                }
                Stack[top]=height[i];
                Stacksize[top++]=cnt;
                if(sa[i]<L1) sum+=tot;
            }
        }
        printf("%lld\n",sum);
    }
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值