HDU 6194 String String String 后缀数组 正好出现K次的子串个数 CSU1632 至少出现2次的子串个数

求正好出现K次的子串个数。
对于 k2 的时候 ,维护一个大小为k-1的区间,LCP(l,r)就是该区间内出现K次的子串个数,因为有些子串可能会在与这个区间的相邻的两端出现,所以要把他们减掉,即贡献是 LCP(l,r)max(height[l1],height[r+1])

对于 k=1 的时候,要求的就是只出现一次的子串个数,联想到后缀数组可以求不同的子串个数,方法是 nsa[i]height[i] ,求不同串个数的时候只减去了左边,所以这个串第一次出现一定会被记录,之后再出现就会被减掉了,要求只出现一次的,就是 nsa[i]max(height[i],height[i+1]) ,简单的理解就是只要在相邻rank中出现过的都不算。

最后模板打错,WA到怀疑人生。。

#include <iostream>
#include <bits/stdc++.h>

using namespace std;
typedef long long LL;
const int MAXN = 1e5+1000;
char s[MAXN];
int num[MAXN],wa[MAXN],wb[MAXN],wv[MAXN],wd[MAXN],rk[MAXN],sa[MAXN],height[MAXN],n,minl[MAXN][20],k;

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

void SA(int *r,int n)
{
    int *x=wa,*y=wb,m=0;
    for (int i=0;i<n;i++) m=max(m,r[i]+1);
    for (int i=0;i<m;i++) wd[i]=0;
    for (int i=0;i<n;i++) ++wd[x[i]=r[i]];
    for (int i=1;i<m;i++) wd[i]+=wd[i-1];
    for (int i=n-1;i>=0;i--) sa[--wd[x[i]]]=i;
    int p=1;
    for (int j=1;p<n;j<<=1,m=p)
    {
        p=0;
        for(int i=n-j; i<n; ++i) y[p++]=i;
        for(int i=0; i<n; ++i) if(sa[i]>=j) y[p++]=sa[i]-j;
        for(int i=0; i<n; ++i) wv[i]=x[y[i]];

        for (int i=0;i<m;i++) wd[i]=0;
        for (int i=0;i<n;i++) ++wd[wv[i]];
        for (int i=1;i<m;i++) wd[i]+=wd[i-1];
        for (int i=n-1;i>=0;i--) sa[--wd[wv[i]]]=y[i];
        swap(x,y); x[sa[0]]=0;p=1;
        for (int i=1;i<n;i++) x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;
    }
    for (int i=1;i<n;i++) rk[sa[i]]=i;
    int k=0;
    for (int i=0;i<n-1;height[rk[i++]]=k)
    {
        if (k)--k;
        for (int j=sa[rk[i]-1];r[i+k]==r[j+k];++k);
    }
}

void initRMQ()
{
    int l=int(log(n)/log(2.0));
    for (int i=1;i<=n;i++) minl[i][0]=height[i];
    for (int j=1;j<=l;j++)
        for (int i=1;i+(1<<(j-1)) <=n ;i++)
            minl[i][j] = min(minl[i][j-1] , minl[i+(1<<(j-1))][j-1]);
}

int askRMQ(int l,int r)
{
    int k=int(log(r-l+1)/log(2));
    return min(minl[l][k] , minl[r-(1<<k)+1][k]);
}

void sov()
{
    LL ans=0;
    if (k>=2)
    {
        int l=2,r=k;
        while (r<=n)
        {
            LL tmp=askRMQ(l,r) - max(height[l-1],height[r+1]);
            if (tmp > 0 ) ans+=tmp;
            l++;r++;
        }
    }
    else
    {
        for (int i=1;i<=n;i++) ans += n-sa[i]-max(height[i],height[i+1]);
    }
    printf("%lld\n",ans);
}


int main()
{
    int t;
    scanf("%d",&t);
    while (t--)
    {
        memset(sa,0,sizeof sa);
        memset(rk,0,sizeof rk);
        memset(height,0,sizeof height);
        scanf("%d%s",&k,s);
        int len=strlen(s);
        n=len;
        for (int i=0;i<n;i++) num[i] =s[i];
        num[n]=0;
        SA(num,n+1);
        initRMQ();
        sov();
    }
    return 0;
}

CSU1632

求至少出现两次的子串个数。
同样的,出现两次,我们先维护一个长度为1的区间,如果是求正好两次,那么就是 height[i]max(height[i1],height[i+1]) ,如果是求出现至少两次,那么就是 height[i]height[i1] 。注意若小于0则不计入答案。
从这两题里我们可以看出,如果只减去前面的,那么求的就是至少K次。如果两边都减,那么就是正好K次。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值