花了近三天的时间终于差不多把后缀数组攻下了,汗啊!
参考的资料:
1.国家集训队2004许智磊的论文《后缀数组》
2.国家集训队2009年罗穗骞的论文《后缀数组——处理字符串的有力工具》
3.byvoid大牛空间的文章《最长公共子串问题的后缀数组解法》
弄懂原理其实还没花多少时间,主要时间花在了代码理解和其细节处理,因为我从不复制粘贴别人的代码,一定要自己亲手敲一遍才放心。代码建议看byvoid大牛空间里的,不管是变量命名还是思路都跟清晰一些,特别是对于第一次学后缀数组的人来说尤其重要!除非是觉得特无聊再去研究09年罗穗骞的论文中的代码,引用poj2774 discuss里面
frkstyc的一句话SA之类只能copy的东西还不如看正式发表的论文
本人性格应该是特固执类型的,所以不弄懂誓不罢休!
思路建议以2004年许智磊的论文为主(文字为主),然后可以结合09年罗穗骞的论文中的图形和部分解释。
poj2774代码
注意以上是不能AC的代码,只需要把上面代码中的这段
换成下面这段代码就行(参考队友的)
本人是坚持自己的代码是正确的,用下面这组随机出来的数组测试就知道了。
jworerrrrr
rrreeeeeeeee
AC的代码得出的答案是8,事实上正确的答案应该是3。
以上内容皆属原创,如有错误欢迎指正!
参考的资料:
1.国家集训队2004许智磊的论文《后缀数组》
2.国家集训队2009年罗穗骞的论文《后缀数组——处理字符串的有力工具》
3.byvoid大牛空间的文章《最长公共子串问题的后缀数组解法》
弄懂原理其实还没花多少时间,主要时间花在了代码理解和其细节处理,因为我从不复制粘贴别人的代码,一定要自己亲手敲一遍才放心。代码建议看byvoid大牛空间里的,不管是变量命名还是思路都跟清晰一些,特别是对于第一次学后缀数组的人来说尤其重要!除非是觉得特无聊再去研究09年罗穗骞的论文中的代码,引用poj2774 discuss里面
frkstyc的一句话SA之类只能copy的东西还不如看正式发表的论文
本人性格应该是特固执类型的,所以不弄懂誓不罢休!
思路建议以2004年许智磊的论文为主(文字为主),然后可以结合09年罗穗骞的论文中的图形和部分解释。
下面就是我研究了09年穗骞的论文中代码之后自己修改的,思路是一样的,只不过对变量的命名更加容易理解罢了,还加了详细的注释,我也只能做这么多了,要是还不理解我也没办法了!
#define _N 200010
struct SuffixArray
{
//SuffixArray::none
static char const NONE='z'+1;//所有字符都不出现的
int *r,*rank,*c,*sa,*secondSa,*first,*height;
//r[i]为输入数据从r[i]>=0
//rank[i]保存每一趟排序后的下标i名次
//secondSa[i]保存名次为i第二关键字的下标
//first[i]在第二关键字排名为i的条件下第一关键字的排名
int n,up;//上界r[i]<up
void init()
{
r=new int[_N];
rank=new int[2*_N];
c=new int[_N];//开辟的空间原则上应该是字符中最大值
sa=new int[_N];
secondSa=new int[2*_N];
first=new int[_N];
height=new int[_N];
}
void input(string s)
{
n=s.size();
up=0;
for(int i=0; i<n; i++)
{
r[i]=s[i]-'a';//纯字母字符串
if(up<r[i])up=r[i];
}
up++;
r[n]=up;//末尾与所有字符不同,用于height数组的计算
}
bool cmp(int *rank,int a,int b,int delta)
{
return rank[a]==rank[b] && rank[a+delta]==rank[b+delta];
}
void calSA()
{
for(int i=0; i<2*n; i++)rank[i]=secondSa[i]=-1;
//cmp函数比较第1,2关键字时避免越界的
//判断开2倍空间并且赋值比所有排名还低的排名(因为这个时无效排名)
//计数排序初使化sa[],rank[]
for(int i=0; i<up; i++)c[i]=0;
for(int i=0; i<n; i++) c[rank[i]=r[i]]++;
for(int i=1; i<up; i++)c[i]+=c[i-1];
for(int i=n-1; i>-1; i--)sa[--c[rank[i]]]=i; //从大到小
int p=0;//p<n都可(进入循环的条件)
for(int halfLen=1; p<n; halfLen<<=1,up=p)
{//倍增时每个字符串的长度的一半为halfLen
p=0;//此处初使化的含义和上面的循环条件不同
//p此处的含义是对secondSa下标的初使化
//循环里的条件的含义是最大的名次如果等于n
//则rank[0,1……n-1]已经唯一了,不需要继续执行了
for(int i=n-halfLen; i<n; i++)secondSa[p++]=i;
//超出范围的下标按下标顺序置其名次为最低(稳定排序)
for(int i=0; i<n; i++)if(sa[i]>=halfLen)secondSa[p++]=sa[i]-halfLen;
//含义是名次为上次排名为i的的序号对应于第二关键字的序号为:sa[i]-halfLen
for(int i=0; i<n; i++)first[i]=rank[secondSa[i]];
//在第二关键字排名为i的条件下第一关键字的排名
for(int i=0; i<up; i++)c[i]=0;//这里上界不是n而是up
for(int i=0; i<n; i++)c[first[i]]++;
for(int i=1; i<up; i++)c[i]+=c[i-1];
for(int i=n-1; i>-1; i--)sa[--c[first[i]]]=secondSa[i]; //从大到小(第二关键字已经是有序)
int *tmp,i;
for(tmp=rank,rank=secondSa,secondSa=tmp,rank[sa[0]]=0,i=p=1; i<n; i++)
{//rank[]其实是根据它自己进行计算
rank[sa[i]]=cmp(secondSa,sa[i],sa[i-1],halfLen)?p-1:p++;
//最后一个字符的用处sa[i]+halLen或sa[i-1]+halfLen会越界!!
//重新筛出排名相等的下标
//若第一,二关键字都不同则排名应该+1
}
}
}
void calHeight()
{
//根据性质h[i]>=h[i-1]-1,height[rank[i]]=h[i]依次计算出h[0],h[1],……h[n-1]
int preRankLable,preLen=0;
//preRankLable表示前一名的下标
//preLen表示下标为cur-1的长度
for(int cur=0; cur<n; cur++)
{
if(rank[cur])
{
for(preLen?--preLen:0,preRankLable=sa[rank[cur]-1]; r[cur+preLen]==r[preRankLable+preLen]; preLen++);
height[rank[cur]]=preLen;
}
else height[0]=preLen=0;
}
}
} suffixArray;
poj2774代码
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
// freopen("data.in","r",stdin);
#define _N 200010
struct SuffixArray
{
//SuffixArray::none
static char const NONE='z'+1;//所有字符都不出现的
int *r,*rank,*c,*sa,*secondSa,*first,*height;
//r[i]为输入数据从r[i]>=0
//rank[i]保存每一趟排序后的下标i名次
//secondSa[i]保存名次为i第二关键字的下标
//first[i]在第二关键字排名为i的条件下第一关键字的排名
int n,up;//上界r[i]<up
void init()
{
r=new int[_N];
rank=new int[2*_N];
c=new int[_N];//开辟的空间原则上应该是字符中最大值
sa=new int[_N];
secondSa=new int[2*_N];
first=new int[_N];
height=new int[_N];
}
void input(string s)
{
n=s.size();
up=0;
for(int i=0; i<n; i++)
{
r[i]=s[i]-'a';//纯字母字符串
if(up<r[i])up=r[i];
}
up++;
r[n]=up;//末尾与所有字符不同,用于height数组的计算
}
bool cmp(int *rank,int a,int b,int delta)
{
return rank[a]==rank[b] && rank[a+delta]==rank[b+delta];
}
void calSA()
{
for(int i=0; i<2*n; i++)rank[i]=secondSa[i]=-1;
//cmp函数比较第1,2关键字时避免越界的
//判断开2倍空间并且赋值比所有排名还低的排名(因为这个时无效排名)
//计数排序初使化sa[],rank[]
for(int i=0; i<up; i++)c[i]=0;
for(int i=0; i<n; i++) c[rank[i]=r[i]]++;
for(int i=1; i<up; i++)c[i]+=c[i-1];
for(int i=n-1; i>-1; i--)sa[--c[rank[i]]]=i; //从大到小
int p=0;//p<n都可(进入循环的条件)
for(int halfLen=1; p<n; halfLen<<=1,up=p)
{//倍增时每个字符串的长度的一半为halfLen
p=0;//此处初使化的含义和上面的循环条件不同
//p此处的含义是对secondSa下标的初使化
//循环里的条件的含义是最大的名次如果等于n
//则rank[0,1……n-1]已经唯一了,不需要继续执行了
for(int i=n-halfLen; i<n; i++)secondSa[p++]=i;
//超出范围的下标按下标顺序置其名次为最低(稳定排序)
for(int i=0; i<n; i++)if(sa[i]>=halfLen)secondSa[p++]=sa[i]-halfLen;
//含义是名次为上次排名为i的的序号对应于第二关键字的序号为:sa[i]-halfLen
for(int i=0; i<n; i++)first[i]=rank[secondSa[i]];
//在第二关键字排名为i的条件下第一关键字的排名
for(int i=0; i<up; i++)c[i]=0;//这里上界不是n而是up
for(int i=0; i<n; i++)c[first[i]]++;
for(int i=1; i<up; i++)c[i]+=c[i-1];
for(int i=n-1; i>-1; i--)sa[--c[first[i]]]=secondSa[i]; //从大到小(第二关键字已经是有序)
int *tmp,i;
for(tmp=rank,rank=secondSa,secondSa=tmp,rank[sa[0]]=0,i=p=1; i<n; i++)
{//rank[]其实是根据它自己进行计算
rank[sa[i]]=cmp(secondSa,sa[i],sa[i-1],halfLen)?p-1:p++;
//最后一个字符的用处sa[i]+halLen或sa[i-1]+halfLen会越界!!
//重新筛出排名相等的下标
//若第一,二关键字都不同则排名应该+1
}
}
}
void calHeight()
{
//根据性质h[i]>=h[i-1]-1,height[rank[i]]=h[i]依次计算出h[0],h[1],……h[n-1]
int preRankLable,preLen=0;
//preRankLable表示前一名的下标
//preLen表示下标为cur-1的长度
for(int cur=0; cur<n; cur++)
{
if(rank[cur])
{
for(preLen?--preLen:0,preRankLable=sa[rank[cur]-1]; r[cur+preLen]==r[preRankLable+preLen]; preLen++);
height[rank[cur]]=preLen;
}
else height[0]=preLen=0;
}
}
} suffixArray;
int main()
{
// freopen("data.in","r",stdin);
suffixArray.init();
string master,pattern;
while( cin>>master>>pattern)
{
int loc=master.size();
suffixArray.input(master+SuffixArray::NONE+pattern);
suffixArray.calSA();
suffixArray.calHeight();
int maxi=0,cur,pre=suffixArray.sa[0];
for(int i=1; i<suffixArray.n; i++)
{
cur=suffixArray.sa[i];
if((loc-cur)*(loc-pre)<0)maxi=max(maxi,suffixArray.height[i]);
pre=cur;
}
cout<<maxi<<endl;
}
return 0;
}
注意以上是不能AC的代码,只需要把上面代码中的这段
int maxi=0,cur,pre=suffixArray.sa[0];
for(int i=1; i<suffixArray.n; i++)
{
cur=suffixArray.sa[i];
if((loc-cur)*(loc-pre)<0)maxi=max(maxi,suffixArray.height[i]);
pre=cur;
}
cout<<maxi<<endl;
换成下面这段代码就行(参考队友的)
int maxi=0;
for(int i=1; i<suffixArray.n; i++)
{
if(maxi<suffixArray.height[i])
{
if(suffixArray.sa[i]>=master.length()+1||suffixArray.sa[i-1]>=master.length()+1)
maxi=suffixArray.height[i];
}
}
cout<<maxi<<endl;
本人是坚持自己的代码是正确的,用下面这组随机出来的数组测试就知道了。
jworerrrrr
rrreeeeeeeee
AC的代码得出的答案是8,事实上正确的答案应该是3。
以上内容皆属原创,如有错误欢迎指正!