HDU-5008 Boring String Problem(k大子串,后缀系列)
这个题似乎没有办法用后缀自动机写。。。
\[\
\]
后缀数组
由于后缀数组已经排好序,所以可对于\(sa[i]\)考虑其贡献的个数为\(n-sa[i]+1-lcp[i-1]\)
考虑个数累前缀和\(Sum[i]\),二分就\(Sum[p]\ge k\)能得知要找的串在哪个后缀\(p\)的前缀集里,但是这个后缀可能不止出现的一次
求得的长度\(len=k-Sum[p-1]+lcp[p-1]\)就是排名再加上已经出现的
所有包含这个串的后缀是一段连续的区间\(l,r\)满足\(\forall_{i\in [l,r]}LCP(sa[i],p)\ge len\)
所以在\([l,r]\)中找出最小的下标即可
#include<bits/stdc++.h>
using namespace std;
#define reg register
typedef long long ll;
typedef unsigned long long ull;
#define rep(i,a,b) for(int i=a,i##end=b;i<=i##end;++i)
#define drep(i,a,b) for(int i=a,i##end=b;i>=i##end;--i)
#define pb push_back
template <class T> inline void cmin(T &a,T b){ ((a>b)&&(a=b)); }
template <class T> inline void cmax(T &a,T b){ ((a<b)&&(a=b)); }
char IO;
template <class T=int> T rd(){
T s=0;
int f=0;
while(!isdigit(IO=getchar())) if(IO=='-') f=1;
do s=(s<<1)+(s<<3)+(IO^'0');
while(isdigit(IO=getchar()));
return f?-s:s;
}
const int N=2e5+10;
int n;
char s[N];
int cnt[N],tmp[N],rk[N],sa[N],lcp[N];
ll Sum[N];
void PreMake(){
memset(cnt,0,800);
rep(i,1,n) cnt[(int)s[i]]++;
rep(i,1,200) cnt[i]+=cnt[i-1];
rep(i,1,n) rk[i]=cnt[(int)s[i]],sa[i]=i;
rep(i,n+1,n*2) rk[i]=0;
for(reg int k=1;k<=n;k<<=1) {
rep(i,0,n) cnt[i]=0;
rep(i,1,n) cnt[rk[i+k]]++;
rep(i,1,n) cnt[i]+=cnt[i-1];
drep(i,n,1) tmp[cnt[rk[i+k]]--]=i;
;
rep(i,0,n) cnt[i]=0;
rep(i,1,n) cnt[rk[i]]++;
rep(i,1,n) cnt[i]+=cnt[i-1];
drep(i,n,1) sa[cnt[rk[tmp[i]]]--]=tmp[i];
;
rep(i,1,n) tmp[sa[i]]=tmp[sa[i-1]]+(rk[sa[i]]!=rk[sa[i-1]]||rk[sa[i]+k]!=rk[sa[i-1]+k]);
rep(i,1,n) rk[i]=tmp[i];
}
rep(i,1,n) lcp[i]=0;
for(reg int i=1,h=0;i<=n;++i) {
if(h) h--;
int j=sa[rk[i]-1];
while(i+h<=n && j+h<=n && s[i+h]==s[j+h]) h++;
lcp[rk[i]-1]=h;
}
rep(i,1,n) Sum[i]=Sum[i-1]+n-sa[i]+1-lcp[i-1];
}
int a[20][N],b[20][N],Log[N];
int main(){
rep(i,2,N-1) Log[i]=Log[i>>1]+1;
while(~scanf("%s",s+1)) {
n=strlen(s+1);
PreMake();
rep(i,1,n) a[0][i]=lcp[i],b[0][i]=sa[i];
rep(i,1,17) {
int len=(1<<(i-1));
rep(j,1,n) a[i][j]=min(a[i-1][j],a[i-1][j+len]),b[i][j]=min(b[i-1][j],b[i-1][j+len]);
}
int a=0,b=0;
rep(i,1,rd()) {
ull x=(rd<ull>()^a^b)+1;
int p=lower_bound(Sum+1,Sum+n+1,x)-Sum;
if(p>n) a=b=0;
else {
int l=p,r=p,len=x-Sum[p-1]+lcp[p-1];
drep(j,17,0) {
if(l-(1<<j)>0 && ::a[j][l-(1<<j)]>=len) l-=(1<<j);
if(r+(1<<j)<=n && ::a[j][r]>=len) r+=(1<<j);
} // 倍增找到所有满足条件的后缀
int d=Log[r-l+1],res=min(::b[d][l],::b[d][r-(1<<d)+1]); // ST表查询最小起始下标
a=res,b=a+len-1;
}
printf("%d %d\n",a,b);
}
}
}
\[\
\]
\[\
\]
后缀树
建树之后直接按照字典序遍历得到即可
最终起始和后缀数组是一样的
#include<bits/stdc++.h>
using namespace std;
#define reg register
typedef long long ll;
typedef unsigned long long ull;
#define rep(i,a,b) for(int i=a,i##end=b;i<=i##end;++i)
#define drep(i,a,b) for(int i=a,i##end=b;i>=i##end;--i)
#define pb push_back
template <class T> inline void cmin(T &a,T b){ ((a>b)&&(a=b)); }
template <class T> inline void cmax(T &a,T b){ ((a<b)&&(a=b)); }
char IO;
template <class T=int> T rd(){
T s=0;
int f=0;
while(!isdigit(IO=getchar())) if(IO=='-') f=1;
do s=(s<<1)+(s<<3)+(IO^'0');
while(isdigit(IO=getchar()));
return f?-s:s;
}
const int N=2e5+10;
int n;
char s[N];
int trans[N][26],link[N],len[N],stcnt,lst,End[N];
struct Edge{
int to,nxt;
} e[N];
int head[N],ecnt;
void AddEdge(int u,int v) {
e[++ecnt]=(Edge){v,head[u]};
head[u]=ecnt;
}
void Init(){
link[0]=-1,len[0]=0;
rep(i,0,stcnt) {
head[i]=0;
rep(j,0,25) trans[i][j]=0;
}
lst=stcnt=ecnt=0;
}
void Extend(int c) {
int cur=++stcnt,p=lst;
End[cur]=len[cur]=len[p]+1;
while(~p && !trans[p][c]) trans[p][c]=cur,p=link[p];
if(p==-1) link[cur]=0;
else {
int q=trans[p][c];
if(len[q]==len[p]+1) link[cur]=q;
else {
int clone=++stcnt;
memcpy(trans[clone],trans[q],104);
End[clone]=End[q];
len[clone]=len[p]+1,link[clone]=link[q];
while(~p && trans[p][c]==q) trans[p][c]=clone,p=link[p];
link[cur]=link[q]=clone;
}
}
lst=cur;
}
ll Sum[N];
int Pos[N],cnt,LstL,LstR;
struct Node{
int x,c;
bool operator < (const Node __) const {
return c<__.c;
}
};
void dfs(int u) {
if(u) {
Sum[cnt+1]=Sum[cnt]+len[u]-len[link[u]];
Pos[++cnt]=u;
}
vector <Node> son;
for(reg int i=head[u];i;i=e[i].nxt) {
int v=e[i].to;
son.pb((Node){v,s[n-End[v]+len[link[v]]+1]});
}
sort(son.begin(),son.end());// 按照字典序遍历
rep(i,0,son.size()-1) {
dfs(son[i].x);
cmax(End[u],End[son[i].x]); // 处理出最前面的位置
}
}
int main(){
while(~scanf("%s",s+1)) {
n=strlen(s+1);
Init();
drep(i,n,1) Extend(s[i]-'a'); // 倒着建SAM就是后缀树了
rep(i,1,stcnt) AddEdge(link[i],i);
cnt=LstL=LstR=0;
dfs(0);
rep(i,1,rd()) {
ull x=(rd<ull>()^LstL^LstR)+1;
int p=lower_bound(Sum+1,Sum+cnt+1,x)-Sum; // 二分找到出现位置
if(p>cnt) puts("0 0"),LstL=LstR=0;
else printf("%d %d\n",LstL=(n-End[Pos[p]]+1),LstR=(n-End[Pos[p]]+len[link[Pos[p]]]+(x-Sum[p-1])));
}
}
}