---------------具体见参考资料.-------------《后缀数组-处理字符串的有力工具 》。
后缀数组三个主要数组:
sa[]--下标为此后缀排名,值为排该名的是誰!
rank[]--下标和值 与 sa数组刚好相反!
height[i]==后缀sa[i] 与后缀 sa[i-1] 的最长公共前缀长度!
常用按height值二分的方法来得到 每两个后缀(因为height只是相邻的后缀的lcp)的最长公共前缀(lcp).
模板 ::
#define MAXN 20010
int n,r[MAXN]; //n为输入数组长度,r为输入数组。
int sa[MAXN];
int wa[MAXN],wb[MAXN],wv[MAXN],wss[MAXN];
int height[MAXN],rank[MAXN];
inline bool cmp(int *r,int a,int b,int len){
return r[a]==r[b]&&r[a+len]==r[b+len];
}
void SA(int n,int m){
int i,j,p,*x=wa,*y=wb,*t;
for(i=0;i<m;i++)
wss[i]=0;
for(i=0;i<n;i++)
wss[x[i]=r[i]]++;
for(i=1;i<m;i++)
wss[i]+=wss[i-1];
for(i=n-1;i>=0;i--)
sa[--wss[x[i]]]=i;
for(j=p=1;p<n;j<<=1,m=p){
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;
}
for(i=0;i<m;i++)
wss[i]=0;
for(i=0;i<n;i++)
wss[wv[i]=x[y[i]]]++;
for(i=1;i<m;i++)
wss[i]+=wss[i-1];
for(i=n-1;i>=0;i--)
sa[--wss[wv[i]]]=y[i];
for(t=x,x=y,y=t,x[sa[0]]=0,p=i=1;i<n;i++)
x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;
}
}
void Height(int n){
int i,j,k=0;
for(i=1;i<=n;i++) //注意sa[0]是以r[n-1]开始的串即0,所以这里忽略sa[0]。
rank[sa[i]]=i;
for(i=0;i<n;height[rank[i++]]=k)
for(k?k--:0,j=sa[rank[i]-1];r[i+k]==r[j+k];k++);
}
应用:
一:单个字符串
1.(可重叠)最长重复子串---因每两个后缀的lcp就是一个子串,所以就是height中的最大值 !
2.(不可重叠)最长重复子串 --就要用二分height来枚举到每两个后缀的lcp,加个长度判断就 ok了!
3.(可重叠)最长重复子串+要出现k次---比1难一点,二分height分组,然后直接求哪个组后缀个数最多就ok 了!因为一个组里的最小也不小于height[i],所以就是个数!
4.不相同子串个数---用n-sa[i] 就得到这么多个子串 ,然后 去掉与前面一个后缀重复的子串,即 n-sa[i]-height[i]
5.求最长回文子串--加一个分隔符在最后,将字符串反写接到后面。然后RMQ预处理height,然后分奇偶枚举每个字符中心,就得到答案了!-详见论文
代码::
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
using namespace std;
#define MAXN 2010
int dp[2010][30];
char r[MAXN],rr[MAXN];
int sa[MAXN];
int wa[MAXN],wb[MAXN],wv[MAXN],ws[MAXN];
int height[MAXN],rk[MAXN];
inline bool cmp(int *r,int a,int b,int len){
return r[a]==r[b]&&r[a+len]==r[b+len];
}
void SA(int n,int m){
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;
for(j=p=1;p<n;j<<=1,m=p){
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;
}
for(i=0;i<m;i++)
ws[i]=0;
for(i=0;i<n;i++)
ws[wv[i]=x[y[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,x[sa[0]]=0,p=i=1;i<n;i++)
x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;
}
}
void Height(int n){
int i,j,k=0;
for(i=0;i<=n;i++) //这里sa[0]为‘\0’开始的子串
rk[sa[i]]=i;
for(i=0;i<n;height[rk[i++]]=k)
for(k?k--:0,j=sa[rk[i]-1];r[i+k]==r[j+k];k++);
}
int init(){
int i,j,len;
memset(height,0,sizeof(height));
len=strlen(rr);
for(i=0;i<len;i++)
r[i]=rr[i];
r[i]='$';
for(j=0;j<len;j++)
r[j+len+1]=r[len-1-j];
r[j+len+1]='\0';
return len*2+1;
}
void st(int n){ //以height的下标分段,每段height最小值保存最小值height对应下标。
int i,j,p,q;
for(i=1;i<=n;i++)
dp[i][0]=i;
for(j=1;j<=(int)(log((double)n)/log(2.0));j++)
for(i=1;i+(1<<j)-1<=n;i++){
p=height[dp[i][j-1]];
q=height[dp[i+(1<<(j-1))][j-1]];
if(p>q)
dp[i][j]=dp[i+(1<<(j-1))][j-1];
else
dp[i][j]=dp[i][j-1];
}
}
int RMQ_MIN(int i,int j){
int tem;
if(i>j){
tem=i;
i=j;
j=tem;
}
i++; //交换后小的要加一
int k=(int)(log((double)(j-i+1))/log(2.0));
return min(height[dp[i][k]],height[dp[j-(1<<k)+1][k]]);
}
void solve(int n){
int i,j,ans=0,s;
st(n);
for(i=0;i<n/2;i++){
j=RMQ_MIN(rk[i],rk[n-1-i]);
if(j*2-1>ans){ //回文串长度为奇数情况
ans=j*2-1;
s=i-j+1;
}
j=RMQ_MIN(rk[i],rk[n-i]);
if(j*2>ans){ //回文串长度为偶数情况
ans=j*2;
s=i-j;
}
}
for(i=s;i<s+ans;i++)
printf("%c",rr[i]);
printf("\n");
}
int main(){
int i,j,n;
scanf("%s",rr);
n=init();
SA(n+1,130);
Height(n);
solve(n);
}
6.连续重复子串
第一种:求一个串最多由几个连续重复串得到。
很简单--看论文。poj2406--用KMP最好!后缀超时了!
第二种:重复次数最多的。poj3963
先不看这图,容易想到这题可以枚举长度下 再每两个(相邻枚举长度)点都枚举下。但这个复杂度为O(n^2)
所以按照 论文说的枚举,解释下那个向前向后匹配,因为像论文那样枚举,那个位置不一定是最佳位置,
就像上图中的 r[6] 与 r[9] 是 aba的中间匹配段,所以这样的话,还要向前匹配。
那么只要l-k%l 就相当于前面可能存在的 匹配段!
#include <iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
using namespace std;
#define MAXN 100100
int dp[MAXN][30];
int n,r[MAXN]; //n为输入数组长度,r为输入数组。
int sa[MAXN];
int wa[MAXN],wb[MAXN],wv[MAXN],wss[MAXN];
int height[MAXN],rank[MAXN];
inline bool cmp(int *r,int a,int b,int len){
return r[a]==r[b]&&r[a+len]==r[b+len];
}
void SA(int n,int m){
int i,j,p,*x=wa,*y=wb,*t;
for(i=0;i<m;i++)
wss[i]=0;
for(i=0;i<n;i++)
wss[x[i]=r[i]]++;
for(i=1;i<m;i++)
wss[i]+=wss[i-1];
for(i=n-1;i>=0;i--)
sa[--wss[x[i]]]=i;
for(j=p=1;p<n;j<<=1,m=p){
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;
}
for(i=0;i<m;i++)
wss[i]=0;
for(i=0;i<n;i++)
wss[wv[i]=x[y[i]]]++;
for(i=1;i<m;i++)
wss[i]+=wss[i-1];
for(i=n-1;i>=0;i--)
sa[--wss[wv[i]]]=y[i];
for(t=x,x=y,y=t,x[sa[0]]=0,p=i=1;i<n;i++)
x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;
}
}
void Height(int n){
int i,j,k=0;
for(i=1;i<=n;i++) //注意sa[0]是以r[n-1]开始的串即0,所以这里忽略sa[0]。
rank[sa[i]]=i;
for(i=0;i<n;height[rank[i++]]=k)
for(k?k--:0,j=sa[rank[i]-1];r[i+k]==r[j+k];k++);
}
int get_min(int a,int b)
{
return a<b?a:b;
}
void rmq(int n){ //以height的下标分段,每段height最小值保存最小值height对应下标。
int i,j,p,q;
for(i=1;i<=n;i++)
dp[i][0]=i;
for(j=1;j<=(int)(log((double)n)/log(2.0));j++)
for(i=1;i+(1<<j)-1<=n;i++){
p=height[dp[i][j-1]];
q=height[dp[i+(1<<(j-1))][j-1]];
if(p>q)
dp[i][j]=dp[i+(1<<(j-1))][j-1];
else
dp[i][j]=dp[i][j-1];
}
}
int lcp(int i,int j){
int tem;
if(i>j){
tem=i;
i=j;
j=tem;
}
i++; //交换后小的要加一
int k=(int)(log((double)(j-i+1))/log(2.0));
return min(height[dp[i][k]],height[dp[j-(1<<k)+1][k]]);
}
char op[MAXN];
int cas=0;
int ans[MAXN];
void solve(int n)
{
int k,step,t,l,cnt,maxx=-1;
for(l=1;l<n;l++)
{
for(int i=0;i+l<n;i+=l)//枚举位置
{
if(r[i]==r[i+l])
{
k=lcp(rank[i],rank[i+l]);
step=k/l+1;
t=i-(l-k%l);
if(t>=0&&k%l)
{
if(lcp(rank[t],rank[t+l])>=k) step++;
}
if(step>maxx)
{
maxx=step;
cnt=0;
ans[cnt++]=l;
}
if(step==maxx) ans[cnt++]=l;//因为同样重复次数下这个长度下,字典序可能更小!
}
}
}
int start;
for(int i=1;i<n;i++)//枚举名次
{
int k=sa[i];//枚举名次,就可以保证 字典序最小!
for(int j=0;j<cnt;j++)
{
int ll=ans[j];
if(lcp(rank[k],rank[k+ll])>=(maxx-1)*ll)
{
start=k;
l=maxx*ll;
i=n;
break;
}
}
}
printf("Case %d: ",++cas);
for(int i=0;i<l;i++) printf("%c",op[start+i]);
printf("\n");
}
int main()
{
while(~scanf("%s",op)&&op[0]!='#')
{
int len=strlen(op);
for(int i=0;i<len;i++) r[i]=op[i]-'a'+1;
r[len]=0;
SA(len+1,30);
Height(len);
rmq(len);
solve(len);
}
return 0;
}
二.两个字符串
1.最长公共子串--就连起两个串,求相邻SA在不同串的height最大的值就好了。
代码::
#include <iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define MAXN 2000050
int n,r[MAXN]; //n为输入数组长度,r为输入数组。
int sa[MAXN];
int wa[MAXN],wb[MAXN],wv[MAXN],wss[MAXN];
int height[MAXN],rank[MAXN];
inline bool cmp(int *r,int a,int b,int len){
return r[a]==r[b]&&r[a+len]==r[b+len];
}
void SA(int n,int m){
int i,j,p,*x=wa,*y=wb,*t;
for(i=0;i<m;i++)
wss[i]=0;
for(i=0;i<n;i++)
wss[x[i]=r[i]]++;
for(i=1;i<m;i++)
wss[i]+=wss[i-1];
for(i=n-1;i>=0;i--)
sa[--wss[x[i]]]=i;
for(j=p=1;p<n;j<<=1,m=p){
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;
}
for(i=0;i<m;i++)
wss[i]=0;
for(i=0;i<n;i++)
wss[wv[i]=x[y[i]]]++;
for(i=1;i<m;i++)
wss[i]+=wss[i-1];
for(i=n-1;i>=0;i--)
sa[--wss[wv[i]]]=y[i];
for(t=x,x=y,y=t,x[sa[0]]=0,p=i=1;i<n;i++)
x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;
}
}
void Height(int n){
memset(height,0,sizeof(height));
int i,j,k=0;
for(i=1;i<=n;i++) //注意sa[0]是以r[n-1]开始的串即0,所以这里忽略sa[0]。
rank[sa[i]]=i;
for(i=0;i<n;height[rank[i++]]=k)
for(k?k--:0,j=sa[rank[i]-1];r[i+k]==r[j+k];k++);
}
char op[MAXN];
int solve(int mid)
{
int ans=0;
for(int i=1;i<=n;i++) //遍历一遍height
{
if((sa[i]<mid&&sa[i-1]>mid)||(sa[i-1]<mid&&sa[i]>mid))//判断是否是两个串的后缀,而不是同一个串的后缀的LCP。
if(height[i] > ans) ans=height[i]; //找最大值就是答案了!
}
return ans;
}
int main()
{
while(~scanf("%s",op))
{
int len=strlen(op);
op[len]='0';
scanf("%s",op+len+1);
n=strlen(op);
op[n]='0';
for(int i=0;i<n;i++) r[i]=op[i]-'a'+50;
SA(n+1,80);
Height(n);
printf("%d\n",solve(len));
}
return 0;
}
2.长度大于k的 公共子串个数。(位置不同的相同子串 答案要+1)
解题思路:若用height分组,然后枚举每个A与一个B子串,B与每一个A子串,就是O(N^2)超时!
那么这里用到了一个 单调栈 来维护一下最小值--(因为每两个后缀的LCP是一段height的最小值);
用栈维护的话能够减少复杂度就是因为 --- 它能够把排名大于B该子串的前面一段中 所有的A串的子串与它的
LCP全部加起来!
然后再对A 做一次相同操作---因为还有A串排名比B串小的,也要算进来!
思想就是如此,具体见代码了!
代码:
#include <stdio.h>
#include <string.h>
#define MAX 200100
int k,n,top;
int len,len1,len2;
__int64 tot,ans,num[MAX];
int st[MAX],arr[MAX];
int wa[MAX],wb[MAX];
int wv[MAX],wn[MAX];
int sa[MAX],rank[MAX],h[MAX];
int cmp(int *r,int a,int b,int l) {
return r[a] == r[b] && r[a+l] == r[b+l];
}
void Da(int *r,int n,int m) {
int i,j,k,p,*t;
int *x = wa,*y = wb;
for (i = 0; i < m; ++i) wn[i] = 0;
for (i = 0; i < n; ++i) wn[x[i]=r[i]]++;
for (i = 1; i < m; ++i) wn[i] += wn[i-1];
for (i = n - 1; i >= 0; --i) sa[--wn[x[i]]] = i;
for (j = 1,p = 1; p < n; j *= 2,m = p) {
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;
for (i = 0; i < n; ++i) wv[i] = x[y[i]];
for (i = 0; i < m; ++i) wn[i] = 0;
for (i = 0; i < n; ++i) wn[wv[i]]++;
for (i = 1; i < m; ++i) wn[i] += wn[i-1];
for (i = n - 1; i >= 0; --i) sa[--wn[wv[i]]] = y[i];
t = x,x = y,y = t,p = 1;
for (x[sa[0]] = 0,i = 1; i < n; ++i)
x[sa[i]] = cmp(y,sa[i-1],sa[i],j) ? p - 1 : p++;
}
}
void CalHeight(int *r,int n) {
int i,j,k = 0;
for (i = 1; i <= n; ++i) rank[sa[i]] = i;
for (i = 0; i < n; h[rank[i++]] = k)
for (k ? k-- : 0,j = sa[rank[i]-1]; r[i+k] == r[j+k];k++);
}
__int64 Solve(int n,int k) {
__int64 i,j,tp,ans = 0;
for (i = 1; i <= n; ++i) { //单调栈处理
if (h[i] < k) tot = top = 0; //分组,小于k的就分为一组,不多做处理
else {
tp = 0; //默认tp = 0
if (sa[i-1] > len1) //如果前面一段是字符串B
tp = 1,tot += h[i] - k + 1; //tp是累计可增加的贡献值
while (top > 0 && st[top] >= h[i]) {
tot -= num[top] * (st[top] - h[i]);
tp += num[top],top--;
}
st[++top] = h[i],num[top] = tp;
if (sa[i] < len1) ans += tot;
}
}
for (i = 1; i <= n; ++i) { //单调栈处理
if (h[i] < k) tot = top = 0;
else {
tp = 0;
if (sa[i-1] < len1)
tp = 1,tot += h[i] - k + 1;
while (top > 0 && st[top] >= h[i]) {
tot -= num[top] * (st[top] - h[i]);
tp += num[top],top--;
}
st[++top] = h[i],num[top] = tp;
if (sa[i] > len1) ans += tot;
}
}
return ans;
}
int main()
{
int i,j,t,cas = 0;
char str1[MAX],str2[MAX];
while (scanf("%d",&k),k) {
scanf("%s%s",str1,str2);
for (i = 0; str1[i]; ++i)
arr[i] = str1[i];
arr[i] = '$',len1 = i,i++;
for (j = 0; str2[j];j++)
arr[i+j] = str2[j];
arr[i+j] = 0,len = i + j;
Da(arr,len+1,150);
CalHeight(arr,len);
ans = Solve(len,k);
printf("%I64d\n",ans);
}
}
三.多个字符串
1.poj3294-出现在超过半数字符串 中的 最长公共子串。
这题目非常考细节,首先RE了很多次,后来又TLE,所以总结下经验:
第一:不要用数组存(可能会改变的)中间结果,特别是当数据特别大时。
就像代码中的二分分组中,只需要判断这个mid满不满足。而不要去存每个mid下的ans。
因为有可能存不下! 只需要得到最佳mid,然后再去求一遍ans就可以了!
第二:后缀数组的离散化数字m的值不可以大于227,其实只有小写字母的话,只要26+n个就行了
这个n就是要用到的 各自互相不同的分隔符的个数。
第三:memset() 不要去更新很大的数组,会超时!
第四:这题目要注意n=1,就直接输出就ok了!
//3688K 1172MS AC 2014-04-07 11:37:52
#include <iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define MAXN 101000
int T;
int n,r[MAXN]; //n为输入数组长度,r为输入数组。
char op[1010];
int sa[MAXN];
int wa[MAXN],wb[MAXN],wv[MAXN],wss[MAXN];
int height[MAXN],rank[MAXN];
inline bool cmp(int *r,int a,int b,int len){
return r[a]==r[b]&&r[a+len]==r[b+len];
}
void SA(int n,int m){
int i,j,p,*x=wa,*y=wb,*t;
for(i=0;i<m;i++)
wss[i]=0;
for(i=0;i<n;i++)
wss[x[i]=r[i]]++;
for(i=1;i<m;i++)
wss[i]+=wss[i-1];
for(i=n-1;i>=0;i--)
sa[--wss[x[i]]]=i;
for(j=p=1;p<n;j<<=1,m=p){
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;
}
for(i=0;i<m;i++)
wss[i]=0;
for(i=0;i<n;i++)
wss[wv[i]=x[y[i]]]++;
for(i=1;i<m;i++)
wss[i]+=wss[i-1];
for(i=n-1;i>=0;i--)
sa[--wss[wv[i]]]=y[i];
for(t=x,x=y,y=t,x[sa[0]]=0,p=i=1;i<n;i++)
x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;
}
}
void Height(int n){
int i,j,k=0;
for(i=1;i<=n;i++)
rank[sa[i]]=i;
for(i=0;i<n;height[rank[i++]]=k)
for(k?k--:0,j=sa[rank[i]-1];r[i+k]==r[j+k];k++);
}
int cas;
int flag[MAXN];
int vis[200];
int ok(int pos)
{
if(vis[flag[pos]]==1) {return false;}
else {vis[flag[pos]]=1;return true;}
}
int judge(int key,int n)
{
memset(vis,0,sizeof(vis));
int tot=0;
for(int i=2;i<=n;i++)
{
if(height[i]<key)
{
memset(vis,0,sizeof(vis));
tot=0;
}
else
{
if(ok(sa[i-1])) tot++;
if(ok(sa[i])) tot++;
if(tot>(T/2)) return 1;
}
}
return 0;
}
void OutAns(int key,int n)
{
memset(vis,0,sizeof(vis));
int tot=0;
for(int i=1;i<=n;i++)
{
if(height[i]<key)
{
memset(vis,0,sizeof(vis));
if(tot>(T/2))
{
for(int j=sa[i-1];j<sa[i-1]+key;j++)
printf("%c",r[j]+'a'-1);
printf("\n");
}
tot=0;
}
else
{
if(ok(sa[i-1])) tot++;
if(ok(sa[i])) tot++;
}
}
}
void solve(int n)
{
cas=0;
int l,r,mid,flag=0,ans;
l=1;r=1010;
while(l<=r)
{
mid=(l+r)>>1;
if(judge(mid,n)==1)
{
ans=mid;
l=mid+1;
flag=1;
}
else r=mid-1;
}
if(flag)
OutAns(ans,n);
else printf("?\n");
}
int main()
{
int flag1=1;
while(~scanf("%d",&T)&&T)
{
int len=-1,sumlen=0,m=28;
for(int i=0;i<T;i++)
{
scanf("%s",op);
len=strlen(op);
for(int j=0;j<len;j++)
{
r[sumlen]=op[j]-'a'+1;
flag[sumlen++]=i;
}
r[sumlen]=m;
flag[sumlen++]=m++;
}
cout<<sumlen<<endl;
r[sumlen]=0;
if(flag1){flag1=0;}
else if(!flag1) {printf("\n");}
if(T==1) {op[len]='\0';printf("%s\n",op);continue;}
SA(sumlen+1,m);
Height(sumlen);
solve(sumlen);
}
return 0;
}
2.spoj--给定n个字符串,求在每个字符串中出现两次且 不重叠 的最长子串!
分析:有了上一题的经验,这一题就简单了!
这里要判断这么几个条件:
1。这个子串在所有串中都出现 即tot记录等于N。
2。要出现两次--其实你去判断不重叠时就已经把这个条件包括进去了!
3。不重叠--就是要位置相减不小于枚举的子串长,这个前面的单个字符串就做过了!
那么首先每枚举一个位置,就先判断它所在的串是否已经满足过条件了!
前者没满足前提下,那么这个串就有以下几个情况:
1.第一次存进来的串。for循环没有执行,直接保存了!为了与后串比较!
2.后面存进来的串 也有几个情况:
1.前面已经出现过的!直接return 0;
2.前面没出现过,那么一一比较存在满足条件的直接return 1;否则保存下来!
#include <iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
using namespace std;
#define MAXN 110000
int n,r[MAXN]; //n为输入数组长度,r为输入数组。
int sa[MAXN];
int wa[MAXN],wb[MAXN],wv[MAXN],wss[MAXN];
int height[MAXN],rank[MAXN];
inline bool cmp(int *r,int a,int b,int len){
return r[a]==r[b]&&r[a+len]==r[b+len];
}
void SA(int n,int m){
int i,j,p,*x=wa,*y=wb,*t;
for(i=0;i<m;i++)
wss[i]=0;
for(i=0;i<n;i++)
wss[x[i]=r[i]]++;
for(i=1;i<m;i++)
wss[i]+=wss[i-1];
for(i=n-1;i>=0;i--)
sa[--wss[x[i]]]=i;
for(j=p=1;p<n;j<<=1,m=p){
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;
}
for(i=0;i<m;i++)
wss[i]=0;
for(i=0;i<n;i++)
wss[wv[i]=x[y[i]]]++;
for(i=1;i<m;i++)
wss[i]+=wss[i-1];
for(i=n-1;i>=0;i--)
sa[--wss[wv[i]]]=y[i];
for(t=x,x=y,y=t,x[sa[0]]=0,p=i=1;i<n;i++)
x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;
}
}
void Height(int n){
int i,j,k=0;
for(i=1;i<=n;i++) //注意sa[0]是以r[n-1]开始的串即0,所以这里忽略sa[0]。
rank[sa[i]]=i;
for(i=0;i<n;height[rank[i++]]=k)
for(k?k--:0,j=sa[rank[i]-1];r[i+k]==r[j+k];k++);
}
char op[10010];
int flag[MAXN];
int vis[20];//判断是否已经满足条件!
int save[20][10010];//保存每个字符串下这些pos位置,以便进行寻找!
int lenth[20];//每个字符串的保存了的pos有几个!
int N;//输入字符串个数
int ok(int pos,int key)
{
if(vis[flag[pos]]==1) return 0;
for(int i=1;i<=lenth[flag[pos]];i++)
{
if(save[flag[pos]][i]==pos) return 0;
else if(fabs(save[flag[pos]][i]-pos)>=key) {vis[flag[pos]]=1;return 1;}
}
save[flag[pos]][ ++lenth[flag[pos]] ]=pos;
return 0;
}
int judge(int key,int n)
{
int tot=0;
memset(vis,0,sizeof(vis));
memset(lenth,0,sizeof(lenth));
for(int i=1;i<=n;i++)
{
if(height[i]<key)
{
memset(vis,0,sizeof(vis));
memset(lenth,0,sizeof(lenth));
tot=0;
}
else
{
if(ok(sa[i-1],key)) tot++;
if(ok(sa[i],key)) tot++;
if(tot==N) return 1;
}
}
return 0;
}
void solve(int n)
{
int mid,l=1,r=10000;
int ans=0;
while(l<=r)
{
mid=(l+r)>>1;
if(judge(mid,n)==1) {ans=mid;l=mid+1;}
else r=mid-1;
}
printf("%d\n",ans);
}
int main()
{
int T;scanf("%d",&T);
while(T--)
{
int len,sumlen=0,m=28;
scanf("%d",&N);
for(int i=0;i<N;i++)
{
scanf("%s",op);
len=strlen(op);
for(int j=0;j<len;j++)
{
r[sumlen]=op[j]-'a'+1;
flag[sumlen++]=i;
}
r[sumlen]=m;
r[sumlen++]=m++;
}
r[sumlen]=0;
SA(sumlen+1,m);
Height(sumlen);
solve(sumlen);
}
return 0;
}
3.pok1226
就把正向的反向的串都连起来,判断下就ok!
//Accepted 508K 16MS C++ 2891B
#include <iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define MAXN 202000
int n,r[MAXN]; //n为输入数组长度,r为输入数组。
int sa[MAXN];
int wa[MAXN],wb[MAXN],wv[MAXN],wss[MAXN];
int height[MAXN],rank[MAXN];
inline bool cmp(int *r,int a,int b,int len){
return r[a]==r[b]&&r[a+len]==r[b+len];
}
void SA(int n,int m){
int i,j,p,*x=wa,*y=wb,*t;
for(i=0;i<m;i++)
wss[i]=0;
for(i=0;i<n;i++)
wss[x[i]=r[i]]++;
for(i=1;i<m;i++)
wss[i]+=wss[i-1];
for(i=n-1;i>=0;i--)
sa[--wss[x[i]]]=i;
for(j=p=1;p<n;j<<=1,m=p){
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;
}
for(i=0;i<m;i++)
wss[i]=0;
for(i=0;i<n;i++)
wss[wv[i]=x[y[i]]]++;
for(i=1;i<m;i++)
wss[i]+=wss[i-1];
for(i=n-1;i>=0;i--)
sa[--wss[wv[i]]]=y[i];
for(t=x,x=y,y=t,x[sa[0]]=0,p=i=1;i<n;i++)
x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;
}
}
void Height(int n){
int i,j,k=0;
for(i=1;i<=n;i++) //注意sa[0]是以r[n-1]开始的串即0,所以这里忽略sa[0]。
rank[sa[i]]=i;
for(i=0;i<n;height[rank[i++]]=k)
for(k?k--:0,j=sa[rank[i]-1];r[i+k]==r[j+k];k++);
}
char op[200];
int vis[200];
int flag[MAXN];
int N;
int ok(int pos)
{
if(vis[flag[pos]]) {return 0;}
else {vis[flag[pos]]=1;return 1;}
}
int judge(int key,int n)
{
int tot=0;
memset(vis,0,sizeof(vis));
for(int i=1;i<=n;i++)
{
if(height[i]<key)
{
memset(vis,0,sizeof(vis));
tot=0;
}
else
{
if(ok(sa[i-1])) tot++;
if(ok(sa[i])) tot++;
if(tot==N) return 1;
}
}
return 0;
}
void solve(int n)
{
int l=1,r=100,ans=0;
while(l<=r)
{
int mid=(l+r)>>1;
if(judge(mid,n)) {ans=mid;l=mid+1;}
else {r=mid-1;}
}
printf("%d\n",ans);
}
int main()
{
int T;scanf("%d",&T);
while(T--)
{
int sumlen=0,m=140;
scanf("%d",&N);
for(int i=0;i<N;i++)
{
scanf("%s",op);
int len=strlen(op);
for(int j=0;j<len;j++)
{
r[sumlen]=op[j];
flag[sumlen++]=i;
}
r[sumlen]=m;
flag[sumlen++]=m++;
for(int j=len-1;j>=0;j--)
{
r[sumlen]=op[j];
flag[sumlen++]=i;
}
r[sumlen]=m;
flag[sumlen++]=m++;
}
//if(N==1) {printf("%s\n",op);continue;}
r[sumlen]=0;
SA(sumlen+1,m);
Height(sumlen);
solve(sumlen);
}
return 0;
}