题意
给定一个字符串\(S\),将\(S\)中本质不同的子串首先按照长度从小到大排序,长度相同时按照字典序从小到大排序形成一个字符串数组。有\(Q\)次询问,每次询问上述\(A\)数组中第\(k\)个串在原串\(S\)中第一次出现的位置。
\(1\le|S|,Q\le 10^6,1\le k\le 10^{12}\)
题解
由于子串首先按照长度排序,再按字典序排序,为了回答某次询问\(k\),我们首先要知道该询问的串的长度是多少,然后在所有串长为这个长度的串中根据字典序找到对应的串。我们对\(S\)的反串建\(SAM\),由于每个节点都表示\(S\)的某个后缀的一段长度连续的前缀,我们可以遍历\(SAM\)上的点,然后用差分数组和前缀和的方式统计出原串中长度小于等于某个长度的的串的总数。我们再令\(parent\)树上边的边权为子节点代表的最短的串比父节点多出来的那一个字符,然后对边按边权从小到大排序,那么此时在\(parent\)树上按照\(dfs\)序遍历\(SAM\)节点就相当于按照字典序从小到大遍历所有的串。并且在长度一定时,\(parent\)树上每个节点代表的串是确定的。于是我们可以将询问离线并从小到大排序,根据询问不断增大当前维护的串长,并在\(dfs\)序上根据差分数组用线段树动态维护在当前串长下哪些节点(对应的串)存在,查询时在线段树上二分即可。
#include <bits/stdc++.h>
using namespace std;
const int N=2000100,M=26;
using ll=long long ;
using pii=pair<int,int>;
char S[N];
int n;
vector<pii> e[N];
int dfn[N],inv[N],cnt=0;
ll tag[N];
vector<pii> b[N];
struct sam{
int fa[N],len[N],lst,gt,ch[N][M],pos[N];
void init(){gt=lst=1;}
void ins(int c,int id){
int f=lst,p=++gt;lst=p;
len[p]=len[f]+1;pos[p]=id;
while(f&&!ch[f][c])ch[f][c]=p,f=fa[f];
if(!f){fa[p]=1;return ;}
int x=ch[f][c],y=++gt;
if(len[x]==len[f]+1){gt--;fa[p]=x;return ;}
len[y]=len[f]+1;
//pos[y]=pos[x];
fa[y]=fa[x];
fa[x]=fa[p]=y;
for(int i=0;i<M;i++)ch[y][i]=ch[x][i];
while(f&&ch[f][c]==x)ch[f][c]=y,f=fa[f];
}
int A[N];
ll c[N];
void rsort(){
for(int i=1;i<=gt;i++){c[i]=0;}
for(int i=1;i<=gt;i++)++c[len[i]];
for(int i=1;i<=gt;i++)c[i]+=c[i-1];
for(int i=gt;i>=1;i--){A[c[len[i]]--]=i;}
for(int i=gt;i>=2;i--){
int u=A[i];
pos[fa[u]]=max(pos[fa[u]],pos[u]);
}
}
void dfs(int u){
dfn[u]=++cnt;inv[cnt]=u;
for(auto&v :e[u]){
dfs(v.first);
}
}
void setup(){
rsort();
for(int i=2;i<=gt;i++){
e[fa[i]].push_back(pii{i,S[pos[i]-len[fa[i]]]-'a'});
}
for(int i=1;i<=gt;i++){
sort(e[i].begin(),e[i].end(),[&](const pii&a,const pii& b){
return a.second<b.second;
});
}
dfs(1);
for(int i=2;i<=gt;i++){
++tag[len[fa[i]]+1];
--tag[len[i]+1];
b[len[fa[i]]+1].push_back(pii{i,1});
b[len[i]+1].push_back(pii{i,-1});
}
tag[0]=0;
for(int i=1;i<=n;i++){tag[i]+=tag[i-1];}
for(int i=1;i<=n;i++){tag[i]+=tag[i-1];}
}
}g;
#define ls o<<1
#define rs o<<1|1
#define mid ((l+r)/2)
int s[N<<2];
void mt(int o){s[o]=s[ls]+s[rs];}
void bd(int o,int l,int r){
s[o]=0;if(l==r)return ;
bd(ls,l,mid);bd(rs,mid+1,r);
}
void upd(int o,int l,int r,int x,int d){
s[o]+=d;if(l==r)return ;
if(x<=mid)upd(ls,l,mid,x,d);
else upd(rs,mid+1,r,x,d);
}
int query(int o,int l,int r,int k){
if(l==r)return l;
return s[ls]>=k?query(ls,l,mid,k):query(rs,mid+1,r,k-s[ls]);
}
pair<ll,int> qu[N];
pii ans[N];
void f1(){
scanf("%s",S+1);
n=strlen(S+1);
for(int i=1;i<=n/2;i++)swap(S[i],S[n-i+1]);
g.init();
for(int i=1;i<=n;i++){
g.ins(S[i]-'a',i);
}
g.setup();
int Q;scanf("%d",&Q);
for(int i=1;i<=Q;i++){
scanf("%lld",&qu[i].first);
qu[i].second=i;
}
sort(qu+1,qu+1+Q,[&](const pair<ll,int>& a,const pair<ll,int>& b){
return a.first<b.first;
});
bd(1,1,g.gt);
int na=1;
for(int i=1;i<=Q;i++){
ll k=qu[i].first;
int id=qu[i].second;
int np=lower_bound(tag+1,tag+1+n,k)-tag;
if(np>n){
ans[id]=pii{-1,-1};
continue;
}
ll res=k-tag[np-1];
while(na<=np){
for(auto& v:b[na]){
if(v.second==1){upd(1,1,g.gt,dfn[v.first],1);}
else{upd(1,1,g.gt,dfn[v.first],-1);}
}
++na;
}
int nb=query(1,1,g.gt,res);
nb=inv[nb];
ans[id]=pii{n-g.pos[nb]+1,n-(g.pos[nb]-np+1)+1};
}
for(int i=1;i<=Q;i++){printf("%d %d\n",ans[i].first,ans[i].second);}
}
int main(){
f1();
return 0;
}