P10995 【MX-J3-T2】Substring 题解

题目传送门
洛谷题解

题目大意

给出一个长度为 n n n 的序列 a a a,其中 1 ∼ n 1 \sim n 1n 在序列 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} al1r1、序列 y y y a l 2 ∼ r 2 a_{l_2 \sim r_2} al2r2,且序列 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 1n 只会在 a a a 中出现一次,不会出现 a i = a j ( 1 ≤ i < j ≤ n ) a_i=a_j(1 \le i < j \le n) ai=aj(1i<jn) 的情况。

根据上面的结论,我们可以将 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=pi1+nbi+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+kpi

算法复杂度为 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;
}

完结撒花!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值