您的位置:首页 > 其它

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

2016-09-16 02:06 435 查看
http://acm.hdu.edu.cn/showproblem.php?pid=5442

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

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

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

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

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

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

也就是从sa
开始往下取下标最小的,直到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
,wb
,Ws
,wv
;
int Rank
,height
;
char s
;
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
;
int aa
;
int sa
;
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
=0;
int ans=solve();
int id1=sa
;

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
=0;
ans=solve();
int id2=n/2-1-sa
;
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
=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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: