后缀数组:
hdu-6704:https://vjudge.net/problem/HDU-6704
题意:
求(l,r)区间子串在母串中第k次出现的位置
解析:
一个子串必定是某个后缀的前缀
排序相邻的后缀他们的前缀一定最相似 => 一种子串必定是一些排序相邻的后缀的公共前缀
用后缀数组得到sa,height,rk等.
对于求子串(1,3)的第k个的开头位置,我们得到一个rk[1],有(1,3)作为前缀的子串一定在rk[1]的前后两边
求两个后缀的最长公共前缀用rmq+height求,lcp(a,b)=min(height[a+1],....,height[b]);
我们用两个二分求出最前端和最后端,得到L,R,(L,R)里的串都的前缀都以子串(1,3)作为前缀的,我们再用主席树求出第k个的位置,即第k小的位置
ac:
#include<bits/stdc++.h>
#define MAXN 100005
using namespace std;
char s[MAXN];
int y[MAXN],x[MAXN],c[MAXN];
int sa[MAXN],rk[MAXN],height[MAXN];
/*后缀数组*********************************************************/
void get_SA(int n,int m)
{
for(int i=0;i<MAXN;i++)
x[i]=y[i]=c[i]=0;
for(int i=1;i<=n;i++) ++c[x[i]=s[i]];
for(int i=2;i<=m;i++) c[i]+=c[i-1];
for(int i=n;i>=1;i--) sa[c[x[i]]--]=i;
for(int k=1;k<=n;k<<=1)
{
int num=0;
for(int i=n-k+1;i<=n;++i) y[++num]=i;
for(int i=1;i<=n;i++) if(sa[i]>k) y[++num]=sa[i]-k;
for(int i=1;i<=m;i++) c[i]=0;
for(int i=1;i<=n;i++) ++c[x[i]];
for(int i=2;i<=m;i++) c[i]+=c[i-1];
for(int i=n;i>=1;i--) sa[c[x[y[i]]]--]=y[i],y[i]=0;
swap(x,y);
x[sa[1]]=1;num=1;
for (int i=2;i<=n;i++)
x[sa[i]]=(y[sa[i]]==y[sa[i-1]] && y[sa[i]+k]==y[sa[i-1]+k])?num:++num;
if(num==n) break;
m=num;
}
}
void get_height(int n)
{
int k=0;
for (int i=1;i<=n;++i) rk[sa[i]]=i;
for (int i=1;i<=n;++i)
{
if (rk[i]==1) continue;
if (k) --k;
int j=sa[rk[i]-1];
while (j+k<=n&&i+k<=n&&s[i+k]==s[j+k]) ++k;
height[rk[i]]=k;
}
}
int mins[MAXN][24];
void buildmin(int n)
{
int tem = (int)floor(log2((double)n));
for(int i=1;i<=n;i++)
mins[i][0]=height[i];
for(int j=1;j<=tem;j++)
for(int i=1;i+(1<<j)-1<=n;i++)
mins[i][j] = min(mins[i][j-1],mins[i+(1<<(j-1))][j-1]);
}
int getlcp(int a,int b,int n)
{
if(a>b) swap(a,b);//要求合法
if(a==b)
return n-a+1;//相等直接返回
a++; //lcp=min(hei[l+1],....hei[r]);
int k = log2(b-a+1);
return min(mins[a][k],mins[b-(1<<k)+1][k]);
}
/*主席树************************************************/
int L[MAXN*25],R[MAXN*25],sum[MAXN*25],root[MAXN];
int tot=0;
int build(int l,int r)
{
int now=++tot;
sum[now]=0;
if(l!=r)
{
int mid=(l+r)>>1;
L[now]=build(l,mid);
R[now]=build(mid+1,r);
}
return now;
}
int update(int pre,int l,int r,int k)
{
int now=++tot;
sum[now]=sum[pre]+1;
L[now]=L[pre],R[now]=R[pre];
if(l!=r)
{
int mid=(l+r)>>1;
if(k<=mid)
L[now]=update(L[pre],l,mid,k);
else
R[now]=update(R[pre],mid+1,r,k);
}
return now;
}
int query(int x,int y,int l,int r,int k)
{
if(l==r)
return l;
int mid=(l+r)>>1;
int num=sum[L[y]]-sum[L[x]];
if(num>=k)
return query(L[x],L[y],l,mid,k);
else
return query(R[x],R[y],mid+1,r,k-num);
}
/********************************************************/
void solve(int a,int b,int k,int n)
{
int len=b-a+1;
int L=rk[a],R=rk[a];
int l=1,r=rk[a]-1;//以(a,b)作为子串的最前端
while(l<=r)
{
int mid=(l+r)>>1;
if(getlcp(mid,rk[a],n)>=len)
{
L=mid;
r=mid-1;
}
else{
l=mid+1;
}
}
l=rk[a]+1,r=n;//以(a,b)作为子串的最后端
while(l<=r)
{
int mid=(l+r)>>1;
if(getlcp(mid,rk[a],n)>=len)
{
R=mid;
l=mid+1;
}
else{
r=mid-1;
}
}
if(R-L+1<k)//没有第k
printf("-1\n");
else{
printf("%d\n",query(root[L-1],root[R],1,n,k));
}
}
void init()
{
tot=0;
memset(mins,0,sizeof(mins));
memset(sum,0,sizeof(sum));
}
int main()
{
int t,n,q,k,a,b;
scanf("%d",&t);
while(t--)
{
init();
scanf("%d%d",&n,&q);
int m,l,r;m=122;
scanf("%s",s+1);
get_SA(n,m);
get_height(n);
buildmin(n);
root[0]=build(1,n);
for(int i=1;i<=n;i++)
root[i]=update(root[i-1],1,n,sa[i]);
while(q--)
{
scanf("%d%d%d",&l,&r,&k);
solve(l,r,k,n);
}
}
return 0;
}
链接:https://vjudge.net/problem/SPOJ-DISUBSTR
题意:求母串有多少个不同的子串
解析:
每个子串一定是某个后缀的前缀,那么原问题等价于求所有后缀之间的不相同子串个数
ans=n*(n+1)/2-height[i]的和
ac:
#include<bits/stdc++.h>
#define ll long long
#define MAXN 1005
using namespace std;
char s[MAXN];
int y[MAXN],x[MAXN],c[MAXN];
int sa[MAXN],rk[MAXN],height[MAXN];
void get_SA(int n,int m)
{
for(int i=0;i<MAXN;i++)
x[i]=y[i]=c[i]=0;
for(int i=1;i<=n;i++) ++c[x[i]=s[i]];
for(int i=2;i<=m;i++) c[i]+=c[i-1];
for(int i=n;i>=1;i--) sa[c[x[i]]--]=i;
for(int k=1;k<=n;k<<=1)
{
int num=0;
for(int i=n-k+1;i<=n;++i) y[++num]=i;
for(int i=1;i<=n;i++) if(sa[i]>k) y[++num]=sa[i]-k;
for(int i=1;i<=m;i++) c[i]=0;
for(int i=1;i<=n;i++) ++c[x[i]];
for(int i=2;i<=m;i++) c[i]+=c[i-1];
for(int i=n;i>=1;i--) sa[c[x[y[i]]]--]=y[i],y[i]=0;
swap(x,y);
x[sa[1]]=1;num=1;
for (int i=2;i<=n;i++)
x[sa[i]]=(y[sa[i]]==y[sa[i-1]] && y[sa[i]+k]==y[sa[i-1]+k])?num:++num;
if(num==n) break;
m=num;
}
}
void get_height(int n)
{
int k=0;
for (int i=1;i<=n;++i) rk[sa[i]]=i;
for (int i=1;i<=n;++i)
{
if (rk[i]==1) continue;
if (k) --k;
int j=sa[rk[i]-1];
while (j+k<=n&&i+k<=n&&s[i+k]==s[j+k]) ++k;
height[rk[i]]=k;
}
}
int main()
{
int n,m,t;
scanf("%d",&t);
while(t--)
{
scanf("%s",s+1);
n=strlen(s+1);m=122;
get_SA(n,m);
get_height(n);
ll ans=((ll)n)*(n+1)/2;
for(int i=2;i<=n;i++)
ans-=height[i];
printf("%lld\n",ans);
}
return 0;
}
https://vjudge.net/problem/SPOJ-REPEATs
题意:
给你一个长N的字符串,现在要你找出该字符串中具有最大循环节数目的连续子串.输出该子串的循环节数目即可
解析:
https://blog.csdn.net/u013480600/article/details/23974119
ac:
#include<bits/stdc++.h>
#define MAXN 1000005
using namespace std;
char s[MAXN];
int y[MAXN],x[MAXN],c[MAXN];
int sa[MAXN],rk[MAXN],height[MAXN];
void get_SA(int n,int m)
{
for(int i=0;i<MAXN;i++)
x[i]=y[i]=c[i]=0;
for(int i=1;i<=n;i++) ++c[x[i]=s[i]];
for(int i=2;i<=m;i++) c[i]+=c[i-1];
for(int i=n;i>=1;i--) sa[c[x[i]]--]=i;
for(int k=1;k<=n;k<<=1)
{
int num=0;
for(int i=n-k+1;i<=n;++i) y[++num]=i;
for(int i=1;i<=n;i++) if(sa[i]>k) y[++num]=sa[i]-k;
for(int i=1;i<=m;i++) c[i]=0;
for(int i=1;i<=n;i++) ++c[x[i]];
for(int i=2;i<=m;i++) c[i]+=c[i-1];
for(int i=n;i>=1;i--) sa[c[x[y[i]]]--]=y[i],y[i]=0;
swap(x,y);
x[sa[1]]=1;num=1;
for (int i=2;i<=n;i++)
x[sa[i]]=(y[sa[i]]==y[sa[i-1]] && y[sa[i]+k]==y[sa[i-1]+k])?num:++num;
if(num==n) break;
m=num;
}
}
void get_height(int n)
{
int k=0;
for (int i=1;i<=n;++i) rk[sa[i]]=i;
for (int i=1;i<=n;++i)
{
if (rk[i]==1) continue;
if (k) --k;
int j=sa[rk[i]-1];
while (j+k<=n&&i+k<=n&&s[i+k]==s[j+k]) ++k;
height[rk[i]]=k;
}
}
int mins[MAXN][24];
void buildmin(int n)
{
int tem = (int)floor(log2((double)n));
for(int i=1;i<=n;i++)
mins[i][0]=height[i];
for(int j=1;j<=tem;j++)
for(int i=1;i+(1<<j)-1<=n;i++)
mins[i][j] = min(mins[i][j-1],mins[i+(1<<(j-1))][j-1]);
}
int querymin(int a,int b)
{
int k = log2(b-a+1);
return min(mins[a][k],mins[b-(1<<k)+1][k]);
}
int getlcp(int a,int b,int n)
{
a=rk[a],b=rk[b];
if(a>b)
swap(a,b);
if(a==b)
return n-a+1;
else
return querymin(a+1,b);
}
void init()
{
memset(s,0,sizeof(s));
memset(mins,0,sizeof(mins));
}
int main()
{
int m,q,l,r,t,n;
scanf("%d",&t);
while(t--)
{
init();
scanf("%d",&n);
char v=getchar();
for(int i=1;i<=n;i++)
scanf("%c",&s[i]),v=getchar();
s[n+1]='\0';
m=128;
get_SA(n,m);
get_height(n);
buildmin(n);
int ans=1;
for(int len=1;len<n;len++)
{
for(int j=1;j+len<=n;j+=len)
{
int k=getlcp(j,j+len,n);
int now=k/len+1;
int st=j-(len-k%len);
if(st>=0)//合法
{
if(getlcp(st,st+len,n)/len+1>now)
now++;
}
ans=max(ans,now);
}
}
printf("%d\n",ans);
}
return 0;
}