rank数组建议写成rk,因为rank极有可能会和某个库函数里的东西冲突。。
要注意不同的模板里面排序的起始标号可能也会不同,有些rank和sa都是从1开始的,而有些是从0开始的。
建议在做题前找一个好的模板,做题过程中熟悉模板。
下面只简单说明后缀数组应用与题目。
1.不同子串个数:
每个子串都是一个后缀的前缀,从排名1开始计算,每个子串的贡献是len - sa[i] - height[i]。len-sa[i]是自己这个子串有多少个前缀,减去height就是去掉和排名在它前面的那个串相同的前缀。
2.不重叠的最长相同子串
二分枚举长度k,把height分成几组,只考虑height>=k的那些组,在每组中 如果 那么就成立。
3.可重叠的最长相同子串
很明显就是
4.最小循环节
这里是
注意必须是等于。
那么就好判断了直接枚举长度k,如果
那么就成立,这里复杂度是nlogn,用KMP的话可以直接O(N)实现。
5.最长回文子串
将串翻转接在后面,并在两个串之间插入一个无关字符将他们隔开。遍历查询每个点的左边与右边的LCP,左边的LCP即在对称过去的翻转串中查询(注意长度为奇偶的查询的位置不同)。
6.最长公共子串
hdu 1403 Longest Common Substring
注意子串是连续的,那么直接把两个串前后接在一起,很容易想到答案就是满足一定条件的height的最大值,这个条件就是suffix(i-1),suffix(i)在两个不同串里面,用sa[i-1]和sa[i]判断一下位置就可以了。
7.长度不小于K的公共子串个数
POJ 3415
把两个字符串接起来,然后加一个没出现过的字符隔开,最暴力的求法就是 的,也就是对每个A或者B,找到rk在它以前的所有串,求他们的贡献LCP。 根据LCP的求法,距离越远的LCP不可能会更大。所以可以用一个单调栈来维护,栈中的height值栈底小栈顶大,具体可以见代码,这里很好的一个地方就是多加了一个cnt,用来统计有效的串,也就是查B的时候A是有效的,查A的时候B是有效的。这样对A和B各自做一次就可以了,因为每个东西最多入栈一次出栈一次,所以复杂度是O(n)的
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
using namespace std;
const int MAXN = 3e5+1000;
typedef long long LL;
char s1[MAXN],s2[MAXN];
int k,n,len1,len2;
pair<int,LL> sta[MAXN];
int sa[MAXN],rk[MAXN],height[MAXN],wa[MAXN],wb[MAXN],wv[MAXN],wd[MAXN],num[MAXN];
int cmp(int *r,int a,int b,int l)
{
return r[a]==r[b] && r[a+l]==r[b+l];
}
void SA(int *r,int n)
{
int *x=wa,*y=wb,m=0;
for (int i=0;i<n;i++) m=max(m,r[i]+1);
for (int i=0;i<m;i++) wd[i]=0;
for (int i=0;i<n;i++) ++wd[x[i]=r[i]];
for (int i=1;i<m;i++) wd[i]+=wd[i-1];
for (int i=n-1;i>=0;i--) sa[--wd[x[i]]]=i;
int p=1;
for (int j=1;p<n;j<<=1,m=p)
{
p=0;
for (int i=n-j;i<n;i++) y[p++]=i;
for (int i=0;i<n;i++) if (sa[i]>=j) y[p++]=sa[i]-j;
for (int i=0;i<n;i++) wv[i]=x[y[i]];
for (int i=0;i<m;i++) wd[i]=0;
for (int i=0;i<n;i++) ++wd[wv[i]];
for (int i=1;i<m;i++) wd[i]+=wd[i-1];
for (int i=n-1;i>=0;i--) sa[--wd[wv[i]]]=y[i];
swap(x,y);x[sa[0]]=0;p=1;
for (int i=1;i<n;i++) x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;
}
for (int i=1;i<n;i++) rk[sa[i]]=i;
int k=0;
for (int i=0;i<n-1;height[rk[i++]]=k)
{
if (k)--k;
for (int j=sa[rk[i]-1];r[i+k]==r[j+k];k++);
}
}
void sov()
{
int cnt=0,top=0;
LL tot=0,ans=0;
for (int i=2;i<=n;i++)
{
if (height[i]<k) tot=top=0;
else
{
cnt=0;
if (sa[i-1]<len1) tot += height[i]-k+1,cnt++;
while (top && sta[top].first>=height[i])
{
tot -= sta[top].second*(sta[top].first-height[i]);
cnt += sta[top].second;
top -- ;
}
sta[++top].first = height[i];
sta[top].second = cnt;
if (sa[i] > len1) ans += tot;
}
}
tot=0,top=0;
for (int i=2;i<=n;i++)
{
if (height[i]<k) tot=top=0;
else
{
cnt=0;
if (sa[i-1]>len1) tot += height[i]-k+1,cnt++;
while (top && sta[top].first>=height[i])
{
tot -= sta[top].second*(sta[top].first-height[i]);
cnt += sta[top].second;
top -- ;
}
sta[++top].first = height[i];
sta[top].second = cnt;
if (sa[i] < len1) ans += tot;
}
}
printf("%lld\n",ans);
}
int main()
{
while (scanf("%d",&k),k)
{
n=0;
scanf("%s%s",s1,s2);
len1=strlen(s1);
for (int i=0;i<len1;i++) num[n++]=s1[i];
num[n++]=150;
len2=strlen(s2);
for (int i=0;i<len2;i++) num[n++]=s2[i];
num[n] = 0;
SA(num,n+1);
sov();
}
return 0;
}
8.至少在k个串中出现过的子串
首先我做的题是POJ 3294
这题的要求是在大于一半的串里面出现过,所以要注意是
然后就是基本的套路,连起来,二分长度,将height分组,看某个组是否个数大于k。
然后强调几点,因为这题串太多了,通过长度来判断位置就比较麻烦,我是用vis给每个下标做一个标记,标记它属于哪个串。
然后字符串连起来的时候要注意,中间加的另外的字符每次都要不一样,而且不能在给的串中出现,否则会影响答案。
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <string>
#include <set>
using namespace std;
const int MAXN = 1100*110;
int num[MAXN],n,k;
char s[110][1010];
int sa[MAXN],rk[MAXN],height[MAXN],wa[MAXN],wb[MAXN],wv[MAXN],wd[MAXN],tag[MAXN];
bool vis[110];
set<string> ss;
int cmp(int *r,int a,int b,int l)
{
return r[a]==r[b] && r[a+l]==r[b+l];
}
void SA(int *r,int n)
{
int *x=wa,*y=wb,m=0;
for (int i=0;i<n;i++) m=max(m,r[i]+1);
for (int i=0;i<m;i++) wd[i]=0;
for (int i=0;i<n;i++) ++wd[x[i]=r[i]];
for (int i=1;i<m;i++) wd[i]+=wd[i-1];
for (int i=n-1;i>=0;i--) sa[--wd[x[i]]]=i;
int p=1;
for (int j=1;p<n;j<<=1,m=p)
{
p=0;
for (int i=n-j;i<n;i++) y[p++]=i;
for (int i=0;i<n;i++) if (sa[i]>=j) y[p++]=sa[i]-j;
for (int i=0;i<n;i++) wv[i]=x[y[i]];
for (int i=0;i<m;i++) wd[i]=0;
for (int i=0;i<n;i++) ++wd[wv[i]];
for (int i=1;i<m;i++) wd[i]+=wd[i-1];
for (int i=n-1;i>=0;i--) sa[--wd[wv[i]]]=y[i];
swap(x,y);x[sa[0]]=0;p=1;
for (int i=1;i<n;i++) x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;
}
for (int i=1;i<n;i++) rk[sa[i]]=i;
int k=0;
for (int i=0;i<n-1;height[rk[i++]]=k)
{
if (k)--k;
for (int j=sa[rk[i]-1];r[i+k]==r[j+k];++k);
}
}
bool judge(int mid)
{
bool flag=false;
int cnt=0;
for (int i=2;i<=n;i++)
{
if (height[i] >= mid)
{
if (!cnt)
{
if (!vis[tag[sa[i-1]]]) cnt++,vis[tag[sa[i-1]]]=true;
if (!vis[tag[sa[i]]]) cnt++,vis[tag[sa[i]]]=true;
}
else
{
if (!vis[tag[sa[i]]]) cnt++,vis[tag[sa[i]]]=true;
}
}
else
{
if (cnt > k/2)
{
if (!flag)
{
ss.clear();
flag=true;
}
string tmp;
tmp.clear();
for (int j=sa[i-1];j<sa[i-1]+mid;j++)
tmp += num[j];
ss.insert(tmp);
}
memset(vis,0,sizeof vis);
cnt=0;
}
}
if (flag) return true;
else return false;
}
int main()
{
while (scanf("%d",&k),k)
{
memset(tag,0,sizeof tag);
ss.clear();
n=0;
int l=1,r=0;
for (int i=1;i<=k;i++)
{
scanf("%s",s[i]);
int len=strlen(s[i]);
r=max(r,len);
for (int j=0;j<len;j++)
{
num[n]=s[i][j];
tag[n]=i;
n++;
}
num[n++]=300+i;
}
num[n-1] = 0;
SA(num,n);
int ans=0;
while (l<=r)
{
int mid=(l+r)>>1;
if (judge(mid))
l=mid+1;
else
r=mid-1;
}
if (ss.size()==0)
printf("?\n");
else
{
for (set<string>::iterator it=ss.begin();it!=ss.end();it++)
{
cout<<*it<<endl;
}
}
printf("\n");
}
return 0;
}
9.最大重复子串
这题要注意的是,先枚举长度,再查询,第一次查询贡献的循环次数是tmp/L +1。因为tmp%L可能不等于0,所以如果在他们两个前面再连上一截可能可以匹配的更长,所以进行第二次查询,然后要注意判断一下边界。至于让字典序最小,我们可以把所有满足条件的长度都放进一个数组,最后从sa[1]开始枚举。这样一定能得到最小的。
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <cmath>
using namespace std;
const int MAXN = 110000;
string s,ss;
int num[MAXN],wa[MAXN],wb[MAXN],wv[MAXN],wd[MAXN],rk[MAXN],sa[MAXN],height[MAXN];
int minl[MAXN][20],n,a[MAXN],top;
int cmp(int *r,int a,int b,int l)
{
return r[a]==r[b] && r[a+l]==r[b+l];
}
void SA(int *r,int n)
{
int *x=wa,*y=wb,m=0;
for (int i=0;i<n;i++) m=max(m,r[i]+1);
for (int i=0;i<m;i++) wd[i]=0;
for (int i=0;i<n;i++) ++wd[x[i]=r[i]];
for (int i=1;i<m;i++) wd[i]+=wd[i-1];
for (int i=n-1;i>=0;i--) sa[--wd[x[i]]]=i;
int p=1;
for (int j=1;p<n;j<<=1,m=p)
{
p=0;
for (int i=n-j;i<n;i++) y[p++]=i;
for (int i=0;i<n;i++) if (sa[i]>=j) y[p++]=sa[i]-j;
for (int i=0;i<n;i++) wv[i]=x[y[i]];
for (int i=0;i<m;i++) wd[i]=0;
for (int i=0;i<n;i++) ++wd[wv[i]];
for (int i=1;i<m;i++) wd[i]+=wd[i-1];
for (int i=n-1;i>=0;i--) sa[--wd[wv[i]]]=y[i];
swap(x,y);x[sa[0]]=0;p=1;
for (int i=1;i<n;i++) x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;
}
for (int i=1;i<n;i++) rk[sa[i]]=i;
int k=0;
for (int i=0;i<n-1;height[rk[i++]]=k)
{
if (k)--k;
for (int j=sa[rk[i]-1];r[i+k]==r[j+k];k++);
}
}
void initRMQ()
{
int l=(int)(log(double(n))/log(2.0));
for (int i=1;i<=n;i++) minl[i][0]=height[i];
for (int j=1;j<=l;j++)
for (int i=1;i+(1<<(j-1))<=n;i++)
minl[i][j] = min(minl[i][j-1],minl[i+(1<<(j-1))][j-1]);
}
int askRMQ(int l,int r)
{
int k=int(log(r-l+1)/log(2.0));
int a1 = min(minl[l][k],minl[r-(1<<k)+1][k]);
return a1;
}
int lcp(int a,int b)
{
a=rk[a],b=rk[b];
if (a>b) swap(a,b);
return askRMQ(a+1,b);
}
int main()
{
int Case = 0;
while (cin>>s && s!="#")
{
ss.clear();
n=0;
int len=s.size();
for (int i=0;i<len;i++) num[n++] = s[i];
num[n] = 0;
SA(num,n+1);
initRMQ();
int ans=-1,anslen;
for (int l=1;l<=len;l++)
{
for (int pos=0;pos+l<n;pos+=l)
{
int tmp = lcp(pos,pos+l);
int cnt = tmp / l + 1;
if (tmp%l)
{
int rest =l-tmp%l;
if (rest>=0)
if (lcp(pos-rest,pos-rest+l) > rest) cnt++;
}
if (cnt > ans)
{
ans=cnt;
top = 0;
a[top++]=l;
}
else if (cnt == ans)
a[top++]=l;
}
}
bool flag=false;
for (int i=1;i<=n && !flag;i++)
{
for (int j=0;j<top && !flag;j++)
{
if (lcp(sa[i],sa[i]+a[j]) >= (ans-1)*a[j])
{
ss = s.substr(sa[i],ans*a[j]);
flag=true;
}
}
}
cout<<"Case "<<++Case<<": "<<ss<<endl;
}
return 0;
}