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

来源:互联网 发布:mysql云数据库购买 编辑:程序博客网 时间:2024/05/29 09:38

求正好出现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