Description
pps又开始dota视频直播了!一群每天被pps虐的蒟蒻决定学习pps的操作技术,他们把pps在这局放的技能记录了下来,每个技能用一个字符表示。经过研究,蒟蒻们发现字典序更大的连招威力更大。于是所有蒟蒻都想学习pps最强的连招。但是他们太弱了,不能学会整个视频里的连招,只能学会陈老师一段区间间内的连招,可是这个他们求不出,于是只好向你求助。为了蒟蒻们不再被pps虐(怎么可能),请你帮帮他们。简化题意:给你一个字符串,
每次询问你一段区间的字典序最大的子串。
Input
第一行是一个字符串S,表示pps放的技能
第二行一个正整数Q,表示询问个数
接下来Q行,每行两个正整数[l,r],表示询问区间[l,r]中的字典序最大的子串。
Output
Q行,每行一个正整数,表示该区间内字典序最大的子串的起始位置。
Sample Input
Lets_go_mod_p!
5
2 2
3 3
2 5
1 10
2 9
Sample Output
2
3
3
3
3
数据范围
1<=|S|<=100000
1<=Q<=100000
1<=l<=r<=|S|
分析:
单个字符串的子串问题,我们可以考虑后缀数组
一开始可能想的比较简单,每个询问,我们只要查找区间内rak最大的后缀即可
(大概因为排名靠后的后缀大概比排名靠前的后缀优秀)
然而事实并不是这样的
0 1 2 3 4 5
s: b a a b b b
rak: 3 1 2 6 5 4
ask[0,3]
最优解显然是: [0,3] b a a b
而不是rak最大的:[3,3] b
那么每个后缀到底在什么情况上回产生贡献nei?
设
i<j
i
<
j
,当前区间为
[l,r]
[
l
,
r
]
rank[i]>rank[j] r a n k [ i ] > r a n k [ j ] , i i 一定比优
rank[i]<rank[j] r a n k [ i ] < r a n k [ j ] 且 lcp(i,j)>=r−j+1 l c p ( i , j ) >= r − j + 1 , i i 一定比优
也就是说 lcp l c p 的长度太长,所以从 i i 开始保留的反而多,所以 i i 比优rank[i]<rank[j] r a n k [ i ] < r a n k [ j ] 且 lcp(i,j)<r−j+1 l c p ( i , j ) < r − j + 1 , j j 一定比优
综上所述,后缀
i
i
产生贡献的区间,其中
j
j
为后面第一个大于
rank[i]
r
a
n
k
[
i
]
的位置
也就是说一个最优值影响的区间是连续的
对于询问的区间我们按照左端点从大到小排序
维护一个栈,栈中的每一个元素对应了一段区间。每次扫到一个左端点后,依次弹出栈顶元素,知道当前这个左端点不会影响到当前栈顶的这个区间了。
还有种情况是可能会影响到最后那个区间的一部分,这样需要在这个区间中二分一下这个区间从哪裂开。
时间复杂度是O(nlogn)O(nlogn)的。
然后可以维护一个栈,将后缀从后向前加入栈中,并且保证栈底的rak最大
栈中的每一个元素对应了一段区间,表示当前元素在区间 [l,r] [ l , r ] 中贡献最大答案
每次扫到一个左端点(设为
L
L
)后,依次弹出栈顶元素,直到当前这个左端点可以包含当前栈顶的这个区间
也就是说,栈中所有元素能够贡献最大答案的区间都在之后
那么当前询问的答案就在栈中了
但是我们不知道询问区间的右端点落到了哪个区间,我们就二分一下,
找到右端点所在区间被
x
x
元素控制,当前询问的答案即为
tip
一开始我的LCP都是这样写的:
int LCP(int x,int y) {
int r=y-x+1;
r=log(r)/log(2);
return min(mn[x][r],mn[y-(1<<r)+1][r]);
}
但是我们在求
[x,y]
[
x
,
y
]
的
lcp
l
c
p
时,实际上求是
min(height[x+1],height[y])
m
i
n
(
h
e
i
g
h
t
[
x
+
1
]
,
h
e
i
g
h
t
[
y
]
)
所以修改如下:
int LCP(int x,int y) {
int r=log(y-x)/log(2);
return min(mn[x+1][r],mn[y-(1<<r)+1][r]);
}
好久不写RMQ,一上来就写错:
for (int i=1;i<20;i++)
for (int j=0;j<len;j++) {
if (j+(1<<i)>len) break;
mn[j][i]=min(mn[j][i-1],mn[j+(1<<(i-1))][i-1]);
}
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
using namespace std;
const int N=100010;
int sa[N],hei[N],rak[N],wx[N],wy[N],cc[N];
int n,m,mn[N][20],ans[N],top;
char s[N];
struct node{
int l,r,num;
};
node Q[N],S[N];
int cmp0(const node &A,const node &B) {
return A.l>B.l; //按照左端点从大到小排序
}
int cmp(int *y,int a,int b,int k,int len) {
int ra1=y[a];
int rb1=y[b];
int ra2=a+k>=len? -1:y[a+k];
int rb2=b+k>=len? -1:y[b+k];
return ra1==rb1&&ra2==rb2;
}
void make_sa(int len) {
int i,j,k,m,p;
int *x=wx,*y=wy;
m=128;
for (i=0;i<m;i++) cc[i]=0;
for (i=0;i<len;i++) cc[x[i]=s[i]]++;
for (i=1;i<m;i++) cc[i]+=cc[i-1];
for (i=len-1;i>=0;i--) sa[--cc[x[i]]]=i;
for (k=1;k<=len;k<<=1) { //k=1
p=0;
for (i=len-k;i<len;i++) y[p++]=i;
for (i=0;i<len;i++) if (sa[i]>=k) y[p++]=sa[i]-k;
for (i=0;i<m;i++) cc[i]=0;
for (i=0;i<len;i++) cc[x[y[i]]]++;
for (i=1;i<m;i++) cc[i]+=cc[i-1];
for (i=len-1;i>=0;i--) sa[--cc[x[y[i]]]]=y[i];
swap(x,y);
x[sa[0]]=0;
p=1;
for (int i=1;i<len;i++)
x[sa[i]]=cmp(y,sa[i-1],sa[i],k,len)? p-1:p++;
if (p>=len) break;
m=p;
}
}
void make_hei(int len) {
for (int i=0;i<len;i++) rak[sa[i]]=i;
hei[0]=0;
int k=0;
for (int i=0;i<len;i++) {
if (!rak[i]) continue;
int j=sa[rak[i]-1];
if (k) k--;
while (s[i+k]==s[j+k]&&i+k<len&&j+k<len) k++;
hei[rak[i]]=k;
}
memset(mn,0x33,sizeof(mn));
for (int i=0;i<len;i++) mn[i][0]=hei[i];
for (int i=1;i<20;i++)
for (int j=0;j<len;j++) {
if (j+(1<<i)>len) break;
mn[j][i]=min(mn[j][i-1],mn[j+(1<<(i-1))][i-1]); //j+(1<<(i-1))
}
}
int LCP(int x,int y) {
if (x>y) swap(x,y);
int r=log(y-x)/log(2);
return min(mn[x+1][r],mn[y-(1<<r)+1][r]);
}
int pd(int x,int y,int z) {
if (rak[x]>rak[y]) return 1; //x更优
int len=LCP(rak[x],rak[y]);
if (len<z-y+1) return 0; //y更优
return 1;
}
int main()
{
scanf("%s",s);
n=strlen(s);
make_sa(n); make_hei(n);
scanf("%d",&m);
for (int i=1;i<=m;i++) {
scanf("%d%d",&Q[i].l,&Q[i].r);
Q[i].l--; Q[i].r--;
Q[i].num=i;
}
sort(Q+1,Q+1+m,cmp0);
top=1;
S[0].l=n; S[1].num=S[1].l=S[1].r=n-1;
int j;
for (j=1;Q[j].l==n-1;j++) ans[Q[j].num]=n;
for (int i=n-2;i>=0&&j<=m;i--) { //插入i
bool flag=0;
int now=top,l,r,mid;
while (top) {
int l=pd(i,S[top].num,S[top].l);
int r=pd(i,S[top].num,S[top].r);
if (l&&r) top--; //i的贡献大于栈顶区间
if (!l&&!r) break;
if (l&&!r) {
flag=1;
break;
}
}
if (flag) {
now=l=S[top].l;
r=S[top].r;
while (l<r) {
mid=(l+r)>>1;
if (pd(i,S[top].num,mid)) now=max(now,mid),l=mid+1;
else r=mid;
}
S[top].l=now+1;
top++;
S[top].r=now;
S[top].l=S[top].num=i;
}
else {
S[++top].num=i;
S[top].l=i;
S[top].r=S[top-1].l-1;
}
while (Q[j].l==i&&j<=m) {
l=1;r=top;now=Q[j].r; //二分右端点所在区间
while (l<r) {
mid=(l+r)>>1;
if (now>=S[mid].l&&now<=S[mid].r) break;
if (now<S[mid].l) l=mid+1;
else r=mid;
}
ans[Q[j].num]=S[(l+r)>>1].num+1;
++j;
}
}
for (int i=1;i<=m;i++) printf("%d\n",ans[i]);
return 0;
}