zoukankan      html  css  js  c++  java
  • 题解 UVA10298 【Power Strings】

    此题我写的是后缀数组SA解法,如果不会后缀数组的可以跳过本篇blog了。

    参考文献:罗穗骞 2009集训队后缀数组论文

    前记

    最近学后缀数组,肝了不少题,也分出了后缀数组的几个题型,看这题没有后缀数组的解法,于是我决定来水一波。

    注:思想正确,代码不一定正确。

    分析题意

    给定一个字符串 L,已知这个字符串是由某个字符串 S 重复 R 次而得到的,
    求 R 的最大值。

    其实就是求字符串L的连续重复子串。连续重复子串就是后缀数组的一个题型。

    算法分析

    1、我们需要最大的R,就说明我们需要连续重复子串的子串长度最小,那我们就可以首先枚举子串的长度k。

    2、如何判断枚举出的子串是否符合题意,为L的连续重复子串。相信学过后缀数组的大家一定知道如何求最长公共前缀,我在这里简要提一下。

    最长公共前缀

    定义&&性质

    一、

    Suf(i)为字符串S的从i位开始的后缀。

    例:S="LAHAKIOI"

    Suf(0)="LAHAKIOI".Suf(4)="AKIOI".(字符串从0开始)

    二、

    height[i]为Suf(SA[i])和Suf(SA[i-1])的最长公共前缀

    例子1

    三、

    h[i]=height[rank[i]],也就是 Suf(i)和在它前一名的后缀的最长公共前
    缀。

    h数组有以下性质:
    h[i]≥h[i-1]-1

    具体证明这里不给出,如果有需要可以看我blog里的后缀数组全讲。

    四、

    Suf(j)和Suf(k)的最长公共前缀为(height[rank[j]+1],height[rank[j]+2],height[rank[j]+3],……,height[rank[k]])的最小值。

    具体证明依然不给出,有需要可以看我的blog。


    正所谓我们使用后缀数组求出了SA数组和Rank数组,我们想要求最长公共前缀,就要高效求出height数组。

    求height数组,我们可以根据h数组的性质(三),对于每一个后缀和他上一个的字符串,我们不需要从头开始寻找他们的最长公共前缀,这样时间复杂度高达O(n^2),我们可以直接从h[i-1]-1处开始判断,因为根据h数组的性质,这两个字符串的前h[i-1]-1位一定是相同的。

    这样时间复杂度降到O(n).

    这里可能大家看的不是很懂,不过代码比较简单,比较容易懂:

    void get_height(){		//求height数组
        int k=0;
        for(int i=0;i<n;i++){
            if(k)k--;       //h数组可以直接用一个计数器代替
            int j=sa[rank[i]-1];
            if(rank[i]-1==-1)continue;  //因为我的排名从0开始,所以排名是0时要特判
            while(s[i+k]==s[j+k])k++;   //从k处找最长公共前缀
            height[rank[i]]=k;          //记录height数组
        }
    }
    

    那我们求出了最长公共前缀就可以判断长度为k的子串是否满足为连续最长子串。

    判断长度为k的子串是否满足为连续最长子串

    1、首先,我们要判断字符串长度n是否能整除k,如果不能就显然不行。

    2、再看Suf(0)到Suf(k)的最长公共前缀是否为n-k。前面根据性质四,我们可以知道Suf(0)到Suf(k)的最长公共前缀,但每一次的时间都为O(n),整体下来时间复杂度十分高。

    但我们可以看到Suf(0)是固定的,所以我们可以预处理出只需求出 height 数组中的每一个数到height[rank[0]]之间的最小值记为ans数组即可。

    全部代码:

    #include<string>
    #include<stdio.h>
    #include<string.h>
    #include<iostream>
    #define maxn 300001
    using namespace std;
    char s[maxn];
    int n,sa[maxn],rank[maxn],newRK[maxn],key2[maxn],sum[maxn],height[maxn],k;
    int level;
    void get_sum(int m){
        for(int i=0;i<=m;i++) sum[i]=0;
        for(int i=0;i<n;i++) sum[rank[i]]++;
        for(int i=0;i<m;i++) sum[i]+=sum[i-1];
    }
    bool cmp(int x,int y,int L){
        if(rank[x]!=rank[y])return false;
        if((x+L>=n&&y+L<n)||(x+L<n&&y+L>=n))return false;
        if(x+L>=n&& y+L>=n) return true;
        return rank[x+L] == rank[y+L];
    }
    void get_height(){		//求height数组
        int k=0;
        for(int i=0;i<n;i++){
            if(k)k--;       //h数组可以直接用一个计数器代替
            int j=sa[rank[i]-1];
            if(rank[i]-1==-1)continue;  //因为我的排名从0开始,所以排名是0时要特判
            while(s[i+k]==s[j+k])k++;   //从k处找最长公共前缀
            height[rank[i]]=k;          //记录height数组
        }
    }
    void Suffix_Sort(){         //SA板子
        for(int i=0;i<n;i++) rank[i]=s[i];
        get_sum(356);
        for(int i=n-1;i>=0;i--)
            sa[--sum[rank[i]]]=i;
        int w=1,m=max(n,356);
        while(w<n){
            int p=0;
            for(int i=n-w;i<n;i++)key2[p++]=i; 	//第二关键字越界排前
            for(int i=0;i<n;i++)if(sa[i]>=w)key2[p++]=sa[i]-w;//如果当前长度有第一关键字就记录
            //以上按第二关键字排序
            get_sum(m);
            for(int i=n-1;i>=0;i--){
                int j=key2[i];
                sa[--sum[rank[j]]]=j;
            }
            //以上按第一关键字排序,直接覆盖之前的sa数组,不需要再开一个key1
            newRK[sa[0]]=0;
            level=1;
            for(int i=1;i<n;i++){
                if(cmp(sa[i-1],sa[i],w))
                    newRK[sa[i]]=level-1;
                else
                    newRK[sa[i]]=level++;
            }
            for(int i=0;i<n;i++)
                rank[i]=newRK[i];
            //以上计算长度2*w的rank数组
            if (level==n)break;
            w<<=1;
        }
    }
    int main(){
        while(1){
            int ans[maxn];
            memset(sa,0,sizeof(sa));
            memset(height,0,sizeof(height));
            memset(ans,0,sizeof(ans));
            memset(rank,0,sizeof(rank));
            scanf("%s",s);n=strlen(s);
            if(s[0]=='.')break;
            Suffix_Sort();get_height();
            //下面求ans数组
            int a=rank[0],minx=10000000;
            for(int i=a;i>=1;i--){
                minx=min(minx,height[i]);
                ans[sa[i-1]]=minx;
            }
            minx=10000000;
            for(int i=a+1;i<=n;i++){
                minx=min(minx,height[i]);
                ans[sa[i]]=minx;
            }
            for(int i=1;i<=n;i++){
                if(n%i==0){
                    if(ans[i]==n-i)
                    {
                        printf("%d
    ",n/i);
                        break;
                    }
                }
            }
        }
    }
    

    谢谢观赏

    点赞嗷 ~ QwQ / *
  • 相关阅读:
    判断touchmove上下的方向
    this的指向
    JS 判断浏览器是否安装Flash 兼容IE、firefox
    Array类型
    addEventListener()与removeEventListener()
    tap 点透问题
    ts 接口
    ts 类型断言
    ts学习
    vue时间戳转换(10位数)/(13位)
  • 原文地址:https://www.cnblogs.com/hyfhaha/p/10678022.html
Copyright © 2011-2022 走看看