题目大意
给出一个长度为 n n n 的序列 a a a,其中 1 ∼ n 1 \sim n 1∼n 在序列 a a a 中各出现一次。
总共有 q q q 次询问,每次询问为序列 a a a 字典序第 k k k 小的子串的左右端点是多少。
思路讲解
设序列 x x x 为 a l 1 ∼ r 1 a_{l_1 \sim r_1} al1∼r1、序列 y y y 为 a l 2 ∼ r 2 a_{l_2 \sim r_2} al2∼r2,且序列 x x x 的长度不超过序列 y y y。
在对 x x x 和 y y y 的字典序进行比较时,不难发现:
-
如果 l 1 = l 2 l_1=l_2 l1=l2 说明 x x x 为 y y y 的前缀,因为 x x x 的长度不超过 y y y 的长度,所以当 r 1 ≠ r 2 r_1 \ne r_2 r1=r2 时, x x x 的字典序小于 y y y 的字典序,否则 x x x 和 y y y 的字典序相等。
-
如果 l 1 ≠ l 2 l_1 \ne l_2 l1=l2,则可以直接比较 a l 1 a_{l_1} al1 和 a l 2 a_{l_2} al2, a l 1 a_{l_1} al1 小则 x x x 的字典序小,否则 y y y 的字典序小。因为 1 ∼ n 1 \sim n 1∼n 只会在 a a a 中出现一次,不会出现 a i = a j ( 1 ≤ i < j ≤ n ) a_i=a_j(1 \le i < j \le n) ai=aj(1≤i<j≤n) 的情况。
根据上面的结论,我们可以将 a a a 的所有子串通过开头数字进行分类,在每一类中将子串以长度从小到大排序。
设
p
i
p_i
pi 为开头数字为
i
i
i 且长度为
1
1
1 的子串的字典序排名,
b
i
b_i
bi 为数字
i
i
i 在
a
a
a 中的下标,则可以很容易地得到
p
i
p_i
pi 的递推公式。
p
i
=
p
i
−
1
+
n
−
b
i
+
1
,
p
1
=
1
p_i=p_{i-1}+n-b_i+1,p_1=1
pi=pi−1+n−bi+1,p1=1
对于每一次询问,由于
p
i
p_i
pi 具有单调性,所以只需二分查找字典序第
k
k
k 小的子串属于哪一类,即第一个小于等于
k
k
k 的
p
i
p_i
pi。找到后,左端点就是
b
i
b_i
bi。由于每一类中的子串的字典序都是以长度排序,所以右端点为
b
i
+
k
−
p
i
b_i+k-p_i
bi+k−pi。
算法复杂度为 O ( q log n ) O(q \log{n}) O(qlogn)。
代码实现
#include <bits/stdc++.h>
using namespace std;
int n,q,a[300005],id[300005];
long long p[300005];
int main(){
//p[i]=开头为i,长度为1的排名
//id[i]为b[i]
cin>>n>>q;
for(int i=1;i<=n;i++){
cin>>a[i];
id[a[i]]=i;
}
long long sum=1;
for(int i=1;i<=n;i++){
p[i]=sum;
sum+=n-id[i]+1;
}
while(q--){
long long t;
cin>>t;
int l=1,r=n;
while(l<r){
int mid=(l+r+1)/2;
if(p[mid]<=t)
l=mid;
else
r=mid-1;
}
cout<<id[l]<<' '<<id[l]+t-p[l]<<'\n';
}
return 0;
}
完结撒花!