hdu 6194 string string string

https://acm.hdu.edu.cn/showproblem.php?pid=6194

 

题意:

给出一个串,询问他有多少个子串恰好出现k次

 

用后缀数组的height数组做

我们枚举按照rank排序的长为k的后缀子区间[l,r]

设这段区间的最长公共前缀位lcp

那么可以得出结论:以sa[l]开始的,长度为1、2、……lcp的子串都至少出现了k次

但是题目要求恰好k次

我们记看第l个和第l-1个后缀的最长公共前缀为p1

第r个和第r+1个后缀的最长公共前缀为p2

那么可以得出结论

以sa[l]开始的,长度为1、2、……max(p1,p2) 的子串都出现了超过k次

所以lcp-max(p1,p2)可以得到以sa[l]开始的恰好出现k次的子串个数

 

 

注意几点:

1、l-1, r+1 如果越界,用0代替

2、我的写法因为height是相邻的两个串的lcp,所以k=1要 特判

3、后缀数组求height数组,在这里比较要用原串,不能用ASCII码,因为字符串结束符ASCII码是0

 

#include<bits/stdc++.h>

using namespace std;

#define N 100009

char ch[N];
int n,k,a[N],v[N],p,q,sa[2][N],rk[2][N],h[N];

multiset<int>s;
multiset<int>::iterator it;

void mul(int *sa,int *rk,int *SA,int *RK)
{
    for(int i=1;i<=n;i++) v[rk[sa[i]]]=i;
    for(int i=n;i;i--)
        if(sa[i]>k) 
            SA[v[rk[sa[i]-k]]--]=sa[i]-k;
    for(int i=n-k+1;i<=n;i++) 
        SA[v[rk[i]]--]=i;
    for(int i=1;i<=n;i++) 
        RK[SA[i]]=RK[SA[i-1]]+(rk[SA[i]]!=rk[SA[i-1]]||rk[SA[i]+k]!=rk[SA[i-1]+k]);
}

void presa()
{
    p=0;
    q=1;
    for(int i=1;i<=127;++i) v[i]=0;
    for(int i=1;i<=n;++i) rk[0][i]=rk[1][i]=0;
    for(int i=1;i<=n;i++) v[a[i]]++;
    for(int i=1;i<=127;i++) v[i]+=v[i-1];
    for(int i=1;i<=n;i++) 
        sa[p][v[a[i]]--]=i;
    for(int i=1;i<=n;i++) 
        rk[p][sa[p][i]]=rk[p][sa[p][i-1]]+(a[sa[p][i-1]]!=a[sa[p][i]]);
    for(k=1;k<n;k<<=1,swap(p,q))
        mul(sa[p],rk[p],sa[q],rk[q]);
    for(int i=1,k=0;i<=n;i++)
    {
        int j=sa[p][rk[p][i]-1];
        while(ch[i+k]==ch[j+k]) k++;
        h[rk[p][i]]=k;if(k) k--;
    }
}

void solve(int m)
{
    h[n+1]=0;
    long long ans=0;
    if(m==1)
    {
        for(int i=1;i<=n;++i) ans+=(n-sa[p][i]+1)-max(h[i],h[i+1]);
        printf("%lld\n",ans);
        return; 
    }
    s.clear();
    for(int i=1;i<m-1;++i) s.insert(h[i+1]);
    int lcp,u,d,last=0;
    for(int i=m;i<=n;++i)
    {    
        s.insert(h[i]);
        it=s.begin();
        lcp=*it;
        u=last;
        d=h[i+1];
        ans+=max(0,lcp-max(u,d));
        last=h[i-m+2];
        it=s.find(last);
        s.erase(it);
    }
    printf("%lld\n",ans); 
}

int main()
{
   int T,m;
   scanf("%d",&T);
   while(T--)
   {
           scanf("%d",&m);
           scanf("%s",ch+1);
           n=strlen(ch+1);
           for(int i=1;i<=n;++i) a[i]=ch[i];
           presa();
           solve(m);
   }
}

 

上一篇:洛谷 P5546 [POI2000]公共串(后缀数组+并查集)


下一篇:利用动态表单实现树形报表