hdu 5442 F - Favorite Donut 后缀数组 / 字符串の最小表示法+kmp

http://acm.hdu.edu.cn/showproblem.php?pid=5442




题意:给出n,和一个数组

通过旋转使得数组[1..N]最后的字典树最大.


对正序的数组复制一遍,跑后缀数组,取rank[]最大的子串下标即为答案。

由于可逆时针,那么需要逆序并复制一遍,需要考虑的时,逆序之后直接取rank[]最大的,其实在原序列里其下标并不是最小的.

那么我们需要取rank前几大的子串长度为n的字典序一样的,取下标最大的一个(正序对应下标最小)

那么怎么取呢,显然就是LCP长度>=n的那几个啦,

也就是从sa[n]开始往下取下标最小的,直到height[]<n..



还有一种解法o(n),最小表示法。

顾名思义,就是把一个字符串,其可通过旋转构造出n个字符串,输出字典序最小的一个,便是其最小表示,通常用于判断同构。

而字符串的最小表示可以有一个o(n)的算法完成。

(参考自http://blog.csdn.net/cclsoft/article/details/5467743 )

首先我们考虑最朴素的比较方法:

-----------------------------------------------------------------------------

给出指针i=0,j=1  (i是最小下标对应的位置,j是下一个用于比较位置)

比较s[i],s[j],

如果s[i]>s[j], i=j,j=i+1  (就是最小开始下标更新为j 的位置嘛)

如果s[i]<s[j],j++

如果s[i]==s[j],设指针k,分别从当前i和j往下比较,直到s[i+k]!=s[j+k] 

如果s[i+k]>s[j+k] 则,i=j,j=i+1

否则j++

当i或j超过n时结束,返回i

------------------------------------------------------------------------------

可以看出,i每次只会被j更新,而这个j在最差的情况下,每移动一格,指针k可能需要移动o(n)格

例如bbbbbbbbbbbbbbbbba这种样例


可以发现,k指针每次只有从头移动到最后的位置,才能使得j++,因此复杂度显然是n^2的。可以如下改进:

-----------------------------------------------------------------------------

给出指针i=0,j=1  (i是最小下标对应的位置,j是下一个用于比较位置)

比较s[i],s[j],

如果s[i]>s[j], i=j,j=i+1  (就是最小开始下标更新为j 的位置嘛)

如果s[i]<s[j],j++

如果s[i]==s[j],设指针k,分别从当前i和j往下比较,直到s[i+k]!=s[j+k] 

如果s[i+k]>s[j+k] 则, i=i+k+1        (也即从i到i+k-1里的任何一个都作为下标,都能在j到j+k-1里找到一个对应的比它更优的起点)

否则j++

当i或j超过n时结束,返回min(i,j) 

------------------------------------------------------------------------------

红色部分的代码,就使得复杂度保证在o(N)了

(AC代码在最后)

【当然啦,剩下的套路和前面一样,正跑一次最大表示法,逆着跑一次,然后逆着的由于下标问题,我们还需要再跑一次kmp得到最小下标】

最大表示法跑了31ms,o(N),后缀数组是nlogn,666ms


猥代码:

int minRepresentation(char*s ,int len)
{
    int i=0,j=1,k=0,t;
    while(i<len&&j<len&&k<len)
    {
        t=s[(i+k)>=len?i+k-len:i+k] - s[(j+k)>=len?j+k-len:j+k];
        if (!t) k++;
        else
        {
            if (t>0) i=i+k+1;
            else j=j+k+1;
            if (i==j)j++;
            k=0;
        }
    }
    return min(i,j);
}






后缀数组:

#include<bits/stdc++.h>
using namespace std;
const int N = 100000+50;
int cmp(int *r,int a,int b,int l)
{
    return (r[a]==r[b]) && (r[a+l]==r[b+l]);
}

int wa[N],wb[N],Ws[N],wv[N];
int Rank[N],height[N];
char s[N];
void DA(int *r,int *sa,int n,int m)  //此处N比输入的N要多1,为人工添加的一个字符,用于避免CMP时越界
{
    int i,j,p,*x=wa,*y=wb,*t;
    for(i=0; i<m; i++) Ws[i]=0;
    for(i=0; i<n; i++) Ws[x[i]=r[i]]++;
    for(i=1; i<m; i++) Ws[i]+=Ws[i-1];
    for(i=n-1; i>=0; i--) sa[--Ws[x[i]]]=i; //预处理长度为1
    for(j=1,p=1; p<n; j*=2,m=p) //通过已经求出的长度J的SA,来求2*J的SA
    {
        for(p=0,i=n-j; i<n; i++) y[p++]=i; // 特殊处理没有第二关键字的
        for(i=0; i<n; i++) if(sa[i]>=j) y[p++]=sa[i]-j; //利用长度J的,按第二关键字排序
        for(i=0; i<n; i++) wv[i]=x[y[i]];
        for(i=0; i<m; i++) Ws[i]=0;
        for(i=0; i<n; i++) Ws[wv[i]]++;
        for(i=1; i<m; i++) Ws[i]+=Ws[i-1];
        for(i=n-1; i>=0; i--) sa[--Ws[wv[i]]]=y[i]; //基数排序部分
        for(t=x,x=y,y=t,p=1,x[sa[0]]=0,i=1; i<n; i++)
            x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;  //更新名次数组x[],注意判定相同的
    }
}

void calheight(int *r,int *sa,int n)  // 此处N为实际长度
{
    int i,j,k=0;        // height[]的合法范围为 1-N, 其中0是结尾加入的字符
    for(i=1; i<=n; i++) Rank[sa[i]]=i; // 根据SA求Rank
    for(i=0; i<n; height[Rank[i++]] = k ) // 定义:h[i] = height[ Rank[i] ]
        for(k?k--:0,j=sa[Rank[i]-1]; r[i+k]==r[j+k]; k++); //根据 h[i] >= h[i-1]-1 来优化计算height过程
}

int n;
char ss[N];
int aa[N];
int sa[N];
int solve()
{
    DA(aa,sa,n+1,128);
    calheight(aa,sa,n);
    int ans=0;
    for (int i=1;i<=n;i++)
    {
        ans+=n-sa[i]-height[i];
    }
    return ans;
}
int main ()
{
    //freopen("input.txt","r",stdin);
    int t;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d",&n); getchar();
        gets(s);
        for (int i=0; i<n; i++)
            s[i+n]=s[i];
        strcpy(ss,s);
        n*=2;
        for (int i=0; i<n; i++)
            aa[i]=ss[i];
        aa[n]=0;
        int ans=solve();
        int id1=sa[n];

        for (int i=0; i<n/2; i++)
            swap(ss[i],ss[n-i-1]);
        for (int i=0; i<n; i++)
            aa[i]=ss[i];
        aa[n]=0;
        ans=solve();
        int id2=n/2-1-sa[n];
        for (int i=n; i>=2; i--)
        {
            if (height[i]>=n/2)
            {
                if (sa[i-1]<(n/2))
                id2=min(id2,n/2-1-sa[i-1]);
            }
            else break;
        }

        int ok=0,x=id1,y=id2;
        for (int i=0; i<n/2; i++)
        {
            if (s[x]>s[y])
            {
                ok=1; break;
            }
            else if(s[x]<s[y])
            {
                ok=2; break;
            }
            x++; y--; if (y<0) y=n-1;
        }
        if (ok==1) printf("%d 0\n",id1+1);
        else if (ok==2) printf("%d 1\n",id2+1);
        else
        {
            if (id1==id2)
                printf("%d 0\n",id1+1);
            else if (id1<id2)
                printf("%d 0\n",id1+1);
            else printf("%d 1\n",id2+1);
        }

    }
    return 0;
}




最大表示法:

-------------------------

#include <cstdio>
#include <cmath>
#include <cstring>
#include <string>
#include <algorithm>
#include <queue>
#include <map>
#include <set>
#include <vector>
#include <iostream>
using namespace std;
const int maxn = 700000;
char pattern[maxn],text[maxn];
int Next[maxn],p[maxn];
void getFail(char pattern[])
{
    int PatLen = strlen(pattern);
    Next[0] = 0;
    Next[1] = 0;
    for(int i=1; i<PatLen; ++i)
    {
        int j = Next[i];
        while(j && pattern[i]!=pattern[j]) j = Next[j];
        Next[i+1] = pattern[i]==pattern[j] ? j+1 : 0;
    }
}

int kmp_times(char text[], char pattern[])
{
    int times = 0;
    int TextLen = strlen(text);
    int PatLen = strlen(pattern);
    int j = 0;
    for(int i=0; i<TextLen; ++i)
    {
        while(j && text[i]!=pattern[j]) j = Next[j];
        if (text[i] == pattern[j]) ++j;
        if (j == PatLen)
        {
            ++times;
            p[times]=i-PatLen+1;
            j = Next[j];
        }
    }
    return times;
}

int maxRepresentation(char*s,int len)
{
    int i=0,j=1,k=0,t;
    while(i<len&&j<len&&k<len)
    {
        t=s[(i+k)>=len?i+k-len:i+k] - s[(j+k)>=len?j+k-len:j+k];
        if (!t) k++;
        else
        {
            if (t<0) i=i+k+1;
            else j=j+k+1;
            if (i==j)j++;
            k=0;
        }
    }
    return min(i,j);
}
char ss[20005];
char rev[20005];
char two[20005*2];
char one[20005];
void change(int n)
{
    for (int i=0; i<n; i++)
        rev[i]=ss[n-1-i];
        rev[n]=0;
}
void doub(int idx2,int n)
{
    int cun=0;
    for (int i=idx2;cun<2*n;)
    {
        two[cun++]=rev[i];
        i=(i+1)%n;
     }
     two[cun]=0;
     cun=0;
    for (int i=idx2;cun<n;)
    {
        one[cun++]=rev[i];
        i=(i+1)%n;
     }
     one[cun]=0;
}
int main()
{
    int t;
    cin>>t;
    while(t--)
    {
        int n;
        cin>>n;
        scanf("%s",ss);
        int idx1=maxRepresentation(ss,n);
        change(n);
        int idx2=maxRepresentation(rev,n);
        doub(idx2,n);
        getFail(one);
        int times=kmp_times(two,one);
        idx2+=p[times-1];
        int flag=0;
        for (int i=idx1,j=idx2,cun=0; cun<n; cun++)
        {
            if (ss[i]>rev[j])
            {
                flag=1;
                break;
            }
            if (ss[i]<rev[j])
            {
                flag=-1;
                break;
            }
            i=(i+1)%n;
            j=(j+1)%n;
        }
        if (flag>0)
            printf("%d 0\n",idx1+1);
        else if (flag<0)
            printf("%d 1\n",n-1-idx2+1);
        else
        {
            idx1++;
            idx2=n-1-idx2+1;
            if (idx1<=idx2)
                printf("%d 0\n",idx1);
            else
                printf("%d 1\n",idx2);

        }


    }


    return 0;
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值