HDU 6194:string string string

参考博客:HDU 6194 string string string (2017沈阳网赛-后缀数组)
下面题解来自该博客
题意:

告诉你一个字符串和k , 求这个字符串中有多少不同的子串恰好出现了k 次。

思路:

后缀数组。

我们先考虑至少出现k 次的子串, 所以我们枚举排好序的后缀i (sa[i]) 。

k段k 段的枚举。

假设当前枚举的是 sa[i]~sa[i + k -1]

那么假设这一段的最长公共前缀 是L 的话。

那么就有L 个不同的子串至少出现了k次。

我们要减去至少出现k + 1次的 , 但还要和这个k 段的lcp 有关系, 因此肯定就是 这一段 向上找一个后缀 或者向下找一个后缀。

即 sa[i-1] ~ sa[i + k - 1] 和 sa[i] ~ sa[i + k] 求两次lcp 减去即可。

但是会减多了。

减多的显然是sa[i-1] ~ sa[i + k] 的lcp。 加上即可。

注意 k =1的情况在求lcp 会有 问题, 即求一个串的最长公共前缀会有问题, 特判一下即可。

一定要注意边界问题 边界问题 边界问题!!!

代码:

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

typedef long long ll;
const int maxn=100000+100;

int sa[maxn],Rank[maxn],lcp[maxn],dis[maxn][20];
int now[maxn],x[maxn],y[maxn],c[maxn];
char ch[maxn];
int n,m;

inline void getSa(){

    for(int i=0;i<m;i++) c[i]=0;
    for(int i=0;i<n;i++) c[x[i]]++;
    for(int i=1;i<m;i++) c[i]+=c[i-1];
    for(int i=n-1;i>=0;i--) sa[--c[x[i]]]=i;

    for(int k=1;k<n;k<<=1){

        int tot=0;
        for(int i=n-k;i<n;i++) y[tot++]=i;
        for(int i=0;i<n;i++) if(sa[i]>=k) y[tot++]=sa[i]-k;

        for(int i=0;i<m;i++) c[i]=0;
        for(int i=0;i<n;i++) c[x[y[i]]]++;
        for(int i=1;i<m;i++) c[i]+=c[i-1];
        for(int i=n-1;i>=0;i--) sa[--c[x[y[i]]]]=y[i];

        swap(x,y);
        int p=0;
        x[sa[0]]=p++;
        for(int 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;
    }

    int k=0;
    for(int i=0;i<n;i++) Rank[sa[i]]=i;
    for(int i=0;i<n-1;i++){

        if(k) k--;
        int j=sa[Rank[i]-1];
        while(now[i+k]==now[j+k]) k++;
        lcp[Rank[i]]=k;
    }
}

inline void Rmq(){

    for(int i=0;i<n;i++) dis[i][0]=lcp[i];
    for(int i=1;(1<<i)<=n;i++){

        for(int j=0;j+(1<<i)-1<n;j++){

            dis[j][i]=min(dis[j][i-1],dis[j+(1<<(i-1))][i-1]);
        }
    }
}

inline int getMin(int l,int r){

    if(l==r) return n-sa[l]-1;
    l++;
    int i=0;
    while((1<<(i+1))<(r-l+1)) i++;
    return min(dis[l][i],dis[r-(1<<i)+1][i]);
}

int main(){

    int T;
    scanf("%d",&T);
    while(T--){

        int num;
        scanf("%d %s",&num,ch);
        n=strlen(ch);
        m=27;
        for(int i=0;i<n;i++) now[i]=x[i]=ch[i]-'a'+1;
        now[n]=x[n]=0;
        n++;
        getSa();
        Rmq();
        ll ans=0;
        for(int i=1;i<=n-num;i++){

            ans+=(ll)getMin(i,i+num-1);
            if(i-1>=1) ans-=(ll)getMin(i-1,i+num-1);
            if(i+num<n)  ans-=(ll)getMin(i,i+num);
            if(i-1>=1 && i+num<n) ans+=(ll)getMin(i-1,i+num);
        }
        printf("%lld\n",ans);
    }
} 

猜你喜欢

转载自blog.csdn.net/qq_37960603/article/details/81285740