【题意】
给定一个字符串 s s s, Q Q Q次询问 s s s本质不同的子串中排名第 k k k的是哪个(输出对应最先出现的区间)。
其中排名的定义是长度小的先,长度相同字典序小的先。
∣ s ∣ , Q ≤ 1 0 6 |s|,Q\leq 10^6 ∣s∣,Q≤106
【思路】
首先这个东西有个字典序的限制,众所周知,我们建出原串的后缀树(即反串SAM的parent树),然后按照边第一个字符从小到大的顺序DFS来遍历整颗树就可以按字典序遍历完所有的子串(当然是边压缩以后的)。
那这个边的第一个字符是什么呢?事实上就是子节点能代表的最长的子串的第一个字符(在我的代码里写的是 s [ r p o s [ i ] − m x [ f a [ i ] ] ] s[rpos[i]-mx[fa[i]]] s[rpos[i]−mx[fa[i]]],其中 s s s是反串)。
一个显然的事实是,后缀树上每一条边可以给一个长度区间的子串数量+1(即 [ m x [ f a [ x ] ] + 1 , m x [ x ] ] [mx[fa[x]]+1,mx[x]] [mx[fa[x]]+1,mx[x]]),那么我们做一个差分,就可以求出每个长度的子串有多少种。
接下来问题转化为了求某个长度的排名第 k k k的子串是什么。
而前面那个显然的事实也可以导出,如果我们将询问离线,按询问子串长度顺序做,我们可以得到所有当前有贡献的边。假设要求长度为 x x x的第 k k k个子串那么只要求出边的DFS序中第 k k k条对 x x x有贡献的边是什么就行了。这个问题可以用线段树上二分解决。
于是总的复杂度 O ( ( n + q ) log n ) O((n+q)\log n) O((n+q)logn)
这个题我使用了十分多的vector于是MLE了,把SAM的表改成map就过了,甚至还能跑的更快?
【参考代码】
#include<bits/stdc++.h>
using namespace std;
#define rep(i,a,b) for(int i=(a),i##end=(b);i<=i##end;i++)
#define dwn(i,a,b) for(int i=(a),i##end=(b);i>=i##end;i--)
#define pb push_back
#define read yh
#define mkp make_pair
#define ll long long
#define fi first
#define se second
#define hvie '\n'
ll yh(){
ll ret=0;bool f=0;char c=getchar();
while(!isdigit(c)){if(c==EOF)return -1;if(c=='-')f=-1;c=getchar();}
while(isdigit(c))ret=ret*10+c-'0',c=getchar();
return f?-ret:ret;
}
typedef pair<int,int> pii;
typedef pair<ll,int> pli;
const int N=2e6+10,M=1e6+10;
int n,Q,cnt;
int cb[N],id[N];
char s[N];
ll sum[M];
pii ans[M];
vector<pii>G[N],vec[N],qr[M];
struct SAM
{
int sz,las,fa[N],mx[N],rpos[N];
map<int,int>ch[N];
void init()
{
sz=las=1;
}
int extend(int c,int tp)
{
int p,q,np,nq;
p=las;las=np=++sz;mx[np]=mx[p]+1;rpos[np]=tp;
while(p && !ch[p].count(c)) ch[p][c]=np,p=fa[p];
if(!p) fa[np]=1;
else
{
q=ch[p][c];
if(mx[p]+1==mx[q]) fa[np]=q;
else
{
nq=++sz;mx[nq]=mx[p]+1;rpos[nq]=tp;
ch[nq]=ch[q];
//memcpy(ch[nq],ch[q],sizeof(ch[q]));
fa[nq]=fa[q];fa[np]=fa[q]=nq;
while(p && ch[p][c]==q) ch[p][c]=nq,p=fa[p];
}
}
return las;
}
void addedge(int x,int y,int w){G[x].pb(mkp(w,y));}
void dfs(int x)
{
sort(G[x].begin(),G[x].end());
for(auto pr:G[x])
{
int v=pr.se;
id[v]=++cnt;cb[cnt]=v;
vec[mx[x]+1].pb(mkp(v,1));vec[mx[v]+1].pb(mkp(v,-1));
dfs(v);
rpos[x]=max(rpos[x],rpos[v]);
}
}
void build()
{
for(int i=2;i<=sz;++i)
{
//printf("%d %d %d\n",fa[i],i,rpos[i]);
++sum[mx[fa[i]]+1],--sum[mx[i]+1];
addedge(fa[i],i,s[rpos[i]-mx[fa[i]]]-'a');
}
for(int i=2;i<=n;++i) sum[i]+=sum[i-1];
for(int i=2;i<=n;++i) sum[i]+=sum[i-1];
dfs(1);
for(int i=1;i<=sz;++i) vector<pii>().swap(G[i]);
//for(int i=1;i<=n;++i) printf("%lld ",sum[i]);puts("");
}
}S;
struct Segment
{
#define ls (x<<1)
#define rs (x<<1|1)
#define mid ((l+r)>>1)
int sum[N<<2];
void update(int x,int l,int r,int p,int val)
{
sum[x]+=val;
if(l==r) return;
if(p<=mid) update(ls,l,mid,p,val);
else update(rs,mid+1,r,p,val);
}
int query(int x,int l,int r,int k)
{
if(l==r) return cb[l];
if(sum[ls]>=k) return query(ls,l,mid,k);
else return query(rs,mid+1,r,k-sum[ls]);
}
#undef ls
#undef rs
#undef mid
}T;
void solve()
{
//T.build(1,1,cnt);
//printf("cnt:%d\n",cnt);
for(int len=1;len<=n;++len)
{
for(auto pr:vec[len])
{
//printf("update:%d %d %d\n",pr.fi,id[pr.fi],pr.se);
T.update(1,1,cnt,id[pr.fi],pr.se);
}
for(auto pr:qr[len])
{
//printf("query:%lld %d\n",pr.fi,pr.se);
int p=T.query(1,1,cnt,pr.fi);
ans[pr.se]=mkp(n-S.rpos[p]+1,n-S.rpos[p]+len);
}
}
for(int i=1;i<=Q;++i) printf("%d %d\n",ans[i].fi,ans[i].se);
}
signed main(){
scanf("%s",s+1);n=strlen(s+1);reverse(s+1,s+n+1);
S.init();
for(int i=1;i<=n;++i) S.extend(s[i]-'a',i);
S.build();
Q=read();
for(int i=1;i<=Q;++i)
{
ll k=read();
if(k>sum[n]) {ans[i]=mkp(-1,-1);continue;}
int p=lower_bound(sum+1,sum+n+1,k)-sum;
//printf("%lld %d\n",k,p);
qr[p].pb(mkp((int)(k-sum[p-1]),i));
}
solve();
return 0;
}