poj3693——后缀数组+RMQ

版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/ZouCharming/article/details/46699583

还是后缀数组,我觉得用后缀数组做字符串的题真真无敌了,今天做字符串练习时,又学到一项新技能,RMQ,加上题目的思路比较巧,就随意做一下总结。。


题目链接:http://poj.org/problem?id=3693


题目大意是给你一个串,要你求重复次数最多的子串


题目的主要思路还是网上的(我还是太菜。。)


给个思路的链接(我觉得讲的挺好):http://m.blog.csdn.net/blog/yanglei040/21179865


大概意思是:遍历所有可能长度,然后进行分段,对相邻的段的端口进行前后匹配,通过匹配长度来确定该匹配的子串最大重复数,看图示挺好理解的


下面说一下RMQ:RMQ使用来求一个范围内的最小值,思想类似于归并排序,就是先两两比较,再用结果进行两两比较,一直递推下去,用一个二维数组存储,能得到任意位置起点的长度为2的幂次范围的最小值


代码如下:

#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
int str[100005],sa[100005],Rank[100005],height[100005],a[100005],b[100005],c[100005],index,len,time;
int RMQ[100005][20];
char temp[100005];
int char2int()
{
    int len=strlen(temp);
    for(int i=0;i<len;i++)
        str[i]=(int)temp[i];
    str[len]=0;
    return len;
}
void DA(int top,int m)
{
    int *x=a,*y=b;
    for(int i=0;i<m;i++)
        c[i]=0;
    for(int i=0;i<top;i++)
        c[x[i]=str[i]]++;
    for(int i=1;i<m;i++)
        c[i]+=c[i-1];
    for(int i=top-1;i>=0;i--)
        sa[--c[x[i]]]=i;
    for(int k=1;k<=top;k*=2)
    {
        int p=0;
        for(int i=top-k;i<top;i++)
            y[p++]=i;
        for(int i=0;i<top;i++)
            if(sa[i]>=k)
                y[p++]=sa[i]-k;
        for(int i=0;i<m;i++)
            c[i]=0;
        for(int i=0;i<top;i++)
            c[x[y[i]]]++;
        for(int i=1;i<m;i++)
            c[i]+=c[i-1];
        for(int i=top-1;i>=0;i--)
            sa[--c[x[y[i]]]]=y[i];
        swap(x,y);
        p=1;
        x[sa[0]]=0;
        for(int i=1;i<top;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>=top)
            break;
        m=p;
    }
}
void getHeight(int top)
{
    for(int i=1;i<=top;i++)
        Rank[sa[i]]=i;
    int t=0;
    for(int i=0;i<top;i++)
    {
        if(t>0)
            t--;
        int j=sa[Rank[i]-1];
        while(str[i+t]==str[j+t])
            t++;
        height[Rank[i]]=t;
    }
}
void init_RMQ(int top)//RMQ[j][i]中j代表起始位置,i代表2^i的长度
{
    for(int i=1;i<=top;i++)
        RMQ[i][0]=height[i];
    for(int i=1;(1<<i)<=top;i++)
        for(int j=1;(j+(1<<i)-1)<=top;j++)
            RMQ[j][i]=min(RMQ[j][i-1],RMQ[j+(1<<(i-1))][i-1]);//利用递推的思想
}
int Query(int l,int r)
{
    if(l>r)
        swap(l,r);
    l++;//排名l和r的后缀的lcp等于min(height[l+1~r]),所以l++
    int k=0;
    while((1<<(k+1))<=r-l+1)
        k++;//增加k值使得整段被覆盖
    return min(RMQ[l][k],RMQ[r-(1<<k)+1][k]);//拆成两段
}
void getIndex(int top)
{
    index=sa[1];
    time=1;
    len=1;
    for(int l=1;l<=top/2;l++)
    {
        for(int i=0;(i+1)*l<top;i++)
        {
            int k=Query(Rank[i*l],Rank[(i+1)*l]);
            int j=1,pos=i*l;//pos代表如果当前查询就是最结果时的起始下标
            while(i*l-j>=0&&str[i*l-j]==str[(i+1)*l-j]&&j<l)//j<l控制不要跨段
            {
                k++;
                if(k%l==0)//表示k/l+1的值增加了1,这时pos一定要更新到第一个下标
                    pos=i*l-j;
                if(Rank[i*l-j]<Rank[pos])//向左匹配时,还要保证字典序尽量小
                    pos=i*l-j;
                j++;
            }
            if(k/l+1>time||(k/l+1==time&&Rank[index]>Rank[pos]))
            {
                index=pos;
                time=k/l+1;
                len=time*l;
            }
        }
    }
}
int main()
{
    int t=1;
    while(1)
    {
        scanf("%s",temp);
        if(temp[0]=='#')
            break;
        int top=char2int();
        DA(top+1,150);
        getHeight(top);
        init_RMQ(top);
        getIndex(top);
        printf("Case %d: ",t++);
        for(int i=index;i<index+len;i++)
            printf("%c",temp[i]);
        printf("\n");
    }
    return 0;
}




展开阅读全文

没有更多推荐了,返回首页