HDU4691用RMQ求所有字符串后缀的最长公共前缀

本文介绍两种不同的字符串匹配算法实现,对比了它们的时间复杂度,并详细展示了如何通过优化降低算法复杂度,提高处理效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

测试证明板子二虽然常数大。但是还是比板子一快的多。

板子1:
复杂度n*log*log
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#define ll long long
#define maxn 100010
using namespace std;
char s[maxn];
int n,k,q;
int rank[maxn],sa[maxn],tmp[maxn],lcp[maxn];//lcp:0-n-1
bool cmp(int x,int y){
    if(rank[x]!=rank[y]) return rank[x]<rank[y];
    int sx=x+k<=n ?

 rank[x+k]:-1;
    int sy=y+k<=n ? rank[y+k]:-1;
    return sx<sy;
}
void build_sa(){
    n=strlen(s);
    for(int i=0;i<=n;i++){
        sa[i]=i;
        rank[i]=i<n ?

 s[i]:-1;
    }
    for(k=1;k<=n;k<<=1){
        sort(sa,sa+n+1,cmp);
        tmp[sa[0]]=0;
        for(int i=1;i<=n;i++){
            tmp[sa[i]]=tmp[sa[i-1]]+(cmp(sa[i-1],sa[i]) ? 1:0);
        }
        for(int i=0;i<=n;i++) rank[i]=tmp[i];
    }
}
void build_lcp(){
    n=strlen(s);
    //for(int i=0;i<=n;i++) rank[sa[i]]=i;
    int h=0;
    lcp[0]=0;
    for(int i=0;i<n;i++){
        int j=sa[rank[i]-1];
        if(h>0) h--;
        for(;j+h<n&&i+h<n;h++){
            if(s[j+h]!=s[i+h]) break;
        }
        lcp[rank[i]-1]=h;
    }
}
int dp[20][maxn],mm[maxn];
void init_RMQ(int n){
    mm[0]=-1;
    for(int i=1;i<=n;i++){//长度1-n
        mm[i]=(i&(i-1)) ? mm[i-1]:mm[i-1]+1;
    }
    for(int i=0;i<n;i++) dp[0][i]=lcp[i];
    for(int i=1;i<=mm[n];i++){
        for(int j=0;j+(1<<i)-1<n;j++){
            dp[i][j]=min(dp[i-1][j],dp[i-1][j+(1<<i>>1)]);
        }
    }
}
int RMQ(int x,int y){//[x,y-1]
    if(x==y) return n-x;
    x=rank[x],y=rank[y];
    if(x>y) swap(x,y);
    y--;
    int l=mm[y-x+1];
    return min(dp[l][x],dp[l][y-(1<<l)+1]);
}
void read(){
    scanf("%d",&q);
    ll sum1=0,sum2=0;
    int pl=-1,pr=-1,l,r;
    for(int i=0;i<q;i++){
        scanf("%d%d",&l,&r);
        sum1+=(r-l+1);
        if(pl==-1){
            sum2+=r-l+1;
        }else{
            int LCP=RMQ(pl,l);
            int ans=min(LCP,min(r-l,pr-pl));
            sum2+=(r-l-ans);
            if(ans==0) sum2+=1;
            else sum2+=(int)log10(ans*1.0)+1;
        }
        pl=l,pr=r;
    }
    printf("%I64d %I64d\n",sum1,sum2+2*q);
}
int main(){
    while(~scanf("%s",s)){
        build_sa();
        build_lcp();
        init_RMQ(n);
        read();
    }
    return 0;
}


板子2:
复杂度:n*log
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#define ll long long
#define maxn 100010
using namespace std;
char s[maxn];
int c[maxn],wa[maxn],wb[maxn],r[maxn];//求SA数组须要的中间变量,不须要赋值
 //待排序的字符串放在s数组中,从s[0]到s[n-1],长度为n,且最大值小于m,
 //除s[n-1]外的全部s[i]都大于0,r[n-1]=0
//函数结束以后结果放在sa数组中
int n,sa[maxn],lcp[maxn],rank[maxn];
bool cmp(int *r,int a,int b,int l){
    return r[a]==r[b]&&r[a+l]==r[b+l];
}
void build_sa(int n,int m){//数组长度,最大数字
    for(int i=0;i<=n;i++) r[i]=i<n ?

 s[i]:0;
    n++;
    int i,j,p,*x=wa,*y=wb;
    //第一轮基数排序。假设s的最大值非常大,可改为高速排序
    for(i=0;i<m;i++) c[i]=0;
    for(i=0;i<n;i++) c[x[i]=r[i]]++;
    for(i=1;i<m;i++) c[i]+=c[i-1];
    for(i=n-1;i>=0;i--) sa[--c[x[i]]]=i;
    for(j=1;j<=n;j<<=1){
        p=0;
        //直接利用sa数组排序第二keyword
        for(i=n-j;i<n;i++) y[p++]=i;//后面的j个数第二keyword为空的最小
        for(i=0;i<n;i++) if(sa[i]>=j) y[p++]=sa[i]-j;
        //这样数组y保存的就是依照第二keyword排序的结果
        //基数排序第一keyword
        for(i=0;i<m;i++) c[i]=0;
        for(i=0;i<n;i++) c[x[y[i]]]++;
        for(i=1;i<m;i++) c[i]+=c[i-1];
        for(i=n-1;i>=0;i--) sa[--c[x[y[i]]]]=y[i];
        //依据sa和x数组计算新的x数组
        swap(x,y);
        p=1,x[sa[0]]=0;
        for(i=1;i<n;i++)
        x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?

p-1:p++;
        if(p>=n) break;
        m=p;
    }
}
void build_lcp(int n){
    int i,j,k=0;
    for(i=0;i<=n;i++) rank[sa[i]]=i;
    lcp[0]=0;
    for(i=0;i<n;i++){
        j=sa[rank[i]-1];
        if(k) k--;
        while(s[i+k]==s[j+k]) k++;
        lcp[rank[i]-1]=k;
    }
}
int dp[20][maxn],mm[maxn];
void init_RMQ(int n){
    mm[0]=-1;
    for(int i=1;i<=n;i++){
        mm[i]=(i&(i-1)) ?

 mm[i-1]:mm[i-1]+1;
    }
    for(int i=0;i<n;i++) dp[0][i]=lcp[i];
    for(int i=1;i<=mm[n];i++){
        for(int j=0;j+(1<<i)-1<n;j++){
            dp[i][j]=min(dp[i-1][j],dp[i-1][j+(1<<i>>1)]);
        }
    }
}
int RMQ(int x,int y){
    if(x==y) return n-x;
    x=rank[x],y=rank[y];
    if(x>y) swap(x,y);
    y--;
    int l=mm[y-x+1];
    return min(dp[l][x],dp[l][y-(1<<l)+1]);
}
int q;
void read(){
    scanf("%d",&q);
    ll sum1=0,sum2=0;
    int pl=-1,pr=-1,l,r;
    for(int i=0;i<q;i++){
        scanf("%d%d",&l,&r);
        sum1+=(r-l+1);
        if(pl==-1){
            sum2+=r-l+1;
        }else{
            int LCP=RMQ(pl,l);
            //cout<<i<<":"<<LCP<<endl;
            int ans=min(LCP,min(r-l,pr-pl));
            //cout<<i<<":"<<ans<<endl;
            sum2+=(r-l-ans);
            if(ans==0) sum2+=1;
            else sum2+=(int)log10(ans*1.0)+1;
        }
        pl=l,pr=r;
    }
    printf("%I64d %I64d\n",sum1,sum2+2*q);
}
int main(){
    while(~scanf("%s",s)){
        n=strlen(s);
        build_sa(n,128);
        build_lcp(n);
        /*for(int i=0;i<n;i++){
            cout<<i<<" "<<sa[i]<<" "<<lcp[i]<<endl;
        }*/
        init_RMQ(n);
        read();
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值