后缀数组
算法思路:倍增+基数排序
数组:sa(第i小的后缀的下标),rk(i后缀是第几小)。
时间复杂度:
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn)
JSOI2007 字符加密(排序循环移动位置)
题意:给出一个串abcde。他有多个循环移动位置:abcde,bcdea,cdeab,…,ebacd。将这些排序。再按从小到大的顺序输出每个串的最后一个字母。
思路:将字串复制为abcdeabcde后计算后缀数组。
#include<bits/stdc++.h>
using namespace std;
const int N = 2e5+5;
char s[N];
int n,sa[N],rk[N],oldrk[N<<1],id[N],px[N],cnt[N];
bool cmp(int x,int y,int w){
return oldrk[x] == oldrk[y] && oldrk[x+w] == oldrk[y+w];
}
int main() {
int i,m = 200,p,w;
scanf("%s",s+1);
n = strlen(s+1);
for(i=1;i<=n;++i) s[i+n]=s[i];
n*=2;
for(i=1;i<=n;++i) ++cnt[rk[i]=s[i]];
for(i=1;i<=m;++i) cnt[i] += cnt[i-1];
for(i=n;i>=1;--i) sa[cnt[rk[i]]--] = i;
for(w=1;;w<<=1,m=p){
for(p=0,i=n;i>n-w;--i) id[++p] = i;
for(i=1;i<=n;++i) if(sa[i] > w) id[++p] = sa[i] - w;
memset(cnt,0,sizeof(cnt));
for(i=1;i<=n;++i) ++cnt[px[i]=rk[id[i]]];
for(i=1;i<=m;++i) cnt[i] += cnt[i-1];
for(i=n;i>=1;--i) sa[cnt[px[i]]--] = id[i];
memcpy(oldrk,rk,sizeof(rk));
for(p=0,i=1;i<=n;++i)
rk[sa[i]] = cmp(sa[i],sa[i-1],w) ? p: ++p;
if(p==n){
for(int i=1;i<=n;++i) sa[rk[i]] = i;
break;
}
}
for(i=1;i<=n;++i){
if(sa[i] > n/2) continue;
printf("%c",s[sa[i] + n/2 - 1]);
}
putchar(10);
}
[USACO06DEC]Milk Patterns(出现次数大于k的最长子串)
思路:
子串是后缀的前缀。
因此我们先进行后缀排序。排序后相邻的k个后缀的最长公共前缀长度即是答案。k个后缀的最长公共前缀长度就是k-1个连续height数组。用单调队列维护答案。
#include<bits/stdc++.h>
using namespace std;
const int N = 2e4+5;
int s[N];
int n,ht[N],sa[N],rk[N],oldrk[N<<1],id[N],px[N],cnt[1000002];
bool cmp(int x,int y,int w){
return oldrk[x] == oldrk[y] && oldrk[x+w] == oldrk[y+w];
}
int main() {
int i,m=1000000,p,w,k;
scanf("%d%d",&n,&k);k--;
for(i=1;i<=n;++i) scanf("%d",&s[i]);
// 后缀排序
for(i=1;i<=n;++i) ++cnt[rk[i]=s[i]];
for(i=1;i<=m;++i) cnt[i] += cnt[i-1];
for(i=n;i>=1;--i) sa[cnt[rk[i]]--] = i;
for(w=1;;w<<=1,m=p){
for(p=0,i=n;i>n-w;--i) id[++p] = i;
for(i=1;i<=n;++i) if(sa[i] > w) id[++p] = sa[i] - w;
memset(cnt,0,sizeof(cnt));
for(i=1;i<=n;++i) ++cnt[px[i]=rk[id[i]]];
for(i=1;i<=m;++i) cnt[i] += cnt[i-1];
for(i=n;i>=1;--i) sa[cnt[px[i]]--] = id[i];
memcpy(oldrk,rk,sizeof(rk));
for(p=0,i=1;i<=n;++i)
rk[sa[i]] = cmp(sa[i],sa[i-1],w) ? p: ++p;
if(p==n){
for(int i=1;i<=n;++i) sa[rk[i]] = i;
break;
}
}
for(i=1,p=0;i<=n;++i){
if(p) --p;
while(s[i+p]==s[sa[rk[i]-1]+p]) ++p;
ht[rk[i]] = p;
}
// 单调队列维护长度为k的区间的height最小值的最大值
// 那么就维护一个递增的数列
int ans = 0;
deque<int> q;
for(i=1;i<=n;++i){
while(q.size() && q.front() <= i-k) q.pop_front();
while(q.size() && ht[q.back()] >= ht[i]) q.pop_back();
q.push_back(i);
if(i>=k) ans = max(ans,ht[q.front()]);
}
printf("%d\n",ans);
}