主席树+后缀数组+ST+二分:HDU6704(CCPC2019网络预选赛,好题)

题目大意:

给你一个字符串 S ( ∣ S ∣ ≤ 1 e 5 ) S(|S| \leq 1e5) S(S1e5).然后给你 Q ( Q ≤ 1 e 5 ) Q(Q\leq1e5) Q(Q1e5)次询问.每次询问一个区间子串 [ L , R ] [L,R] [L,R],记作 S ′ S' S.问你该子串从左到右的第 k k k次出现的位置.

题目思路:

前置知识: O ( l o g n ) O(logn) O(logn)求一个子串在总串中出现的次数.

由于一个子串一定是一个后缀的前缀.所以子串在后缀排序后出现也一定是连续的.

h e i g h t height height数组建ST表,在 r k [ L ] rk[L] rk[L]处向两边二分找最远的 min ⁡ { h e i g h t } ≥ R − L + 1 \min \{height\} \geq R-L+1 min{ height}RL+1的区间范围(就是一个求 L C P LCP LCP的过程).这个时候的区间就是该子串所出现的所有次数了.

现在题目要求这些子串的第 k k k次出现的位置.我们可以对SA数组建主席树.求解区间第 k k k大即可.

AC代码:

#include<bits/stdc++.h>
using namespace std;
#define ri register int
const int maxn = 1e5 + 5;
namespace SA
{
    
    
    char s[maxn<<1];
    int sa[maxn<<1],rk[maxn<<1],sec[maxn<<1],tax[maxn<<1] , n , m;
    int ht[maxn];
    void init (){
    
    
        for (int i = 0 ; i <= 2 * n ; i++)
            s[i] = sa[i] = rk[i] = sec[i] = tax[i] = 0;
    }
    void Solve (char a[] , int len){
    
    
	// 记得当数组是整数数组时, m = n + 5
        m = 300/*字符ascii码最大取值*/ , n = len;
        init();
        for (int i = 1 ; i <= n ; i++) s[i] = a[i];
        int *rnk=rk,*sc=sec;
        for(ri i=1;i<=m;++i) tax[i]=0;
        for(ri i=1;i<=n;++i) ++tax[rnk[i]=s[i]];
        for(ri i=1;i<=m;++i)tax[i]+=tax[i-1];
        for(ri i=n;i>=1;--i)sa[tax[rnk[i]]--]=i;
        for(ri k=1,p=0;k<=n;k<<=1,p=0){
    
    
            for(ri i=n-k+1;i<=n;++i)sc[++p]=i;
            for(ri i=1;i<=n;++i) if(sa[i]>k)sc[++p]=sa[i]-k;
            for(ri i=1;i<=m;++i) tax[i]=0;
            for(ri i=1;i<=n;++i) ++tax[rnk[sc[i]]];
            for(ri i=1;i<=m;++i) tax[i]+=tax[i-1];
            for(ri i=n;i>=1;--i) sa[tax[rnk[sc[i]]]--]=sc[i];
            swap(rnk,sc);p=rnk[sa[1]]=1;
            for(ri i=2;i<=n;++i)rnk[sa[i]]=(sc[sa[i]]==sc[sa[i-1]]&&sc[sa[i]+k]==sc[sa[i-1]+k]?p:++p);//debug(rnk,sc);
            if(p>=n)break;else m=p;
        }
        for(ri i=1;i<=n;++i)rk[sa[i]]=i;
    }
    void getHeight()
    {
    
    
        int i , k;
        for (i = 1, k = 0; i <= n; ++i) {
    
    
            if (k) --k;
            while (s[i + k] == s[sa[rk[i] - 1] + k]) ++k;
            ht[rk[i]] = k;
        }
    }
}
int minn[21][maxn] , Log2[maxn];
void st(int n){
    
    
    Log2[1] = 0;
    for (int i = 2 ; i <= n ; i++)
        Log2[i] = Log2[i >> 1] + 1;
    for(int i=1;i<=20;i++)
        for(int j=1;j+(1<<i)<=n+1;j++)
            minn[i][j]=min(minn[i-1][j],minn[i-1][j+(1<<(i-1))]);
}
int query_minn(int l,int r){
    
    
    int k=Log2[r-l+1];
    return min(minn[k][l],minn[k][r-(1<<k)+1]);
}
#define mid ((l + r) >> 1)
int sum[maxn << 5] , ls[maxn << 5] , rs[maxn << 5] , rt[maxn] , tot;
int add (int l , int r , int t , int p , int c)
{
    
    
    int now = ++tot;
    ls[now] = ls[t];
    rs[now] = rs[t];
    sum[now] = sum[t] + c;
    if (l == r) return now;
    if (p <= mid) ls[now] = add(l , mid , ls[now] , p , c);
    else rs[now] = add(mid + 1 , r , rs[now] , p , c);
    return now;
}
int ask (int u , int v , int l , int r , int k)
{
    
    
    if (l == r) return l;
    int d = sum[ls[v]] - sum[ls[u]];
    if (d >= k) return ask(ls[u] , ls[v] , l , mid , k);
    return ask(rs[u] , rs[v] , mid + 1 , r , k - d);
}
char a[maxn];
int main()
{
    
    
    int t; scanf("%d" , &t);
    while (t--){
    
    
        int n , q;scanf("%d%d" , &n , &q);
        SA::init();
        scanf("%s" , a + 1);
        SA::Solve(a , n);
        SA::getHeight();
        for (int i = 1; i <= n ; i++){
    
    
            minn[0][i] = SA::ht[i];
        }
        st(n);
        tot = rt[0] = 0;
        for (int i = 1; i <= n ; i++){
    
    
            rt[i] = add(1 , n , rt[i - 1] , SA::sa[i] , 1);
        }
        while (q--){
    
    
            int x , y , k; scanf("%d%d%d" , &x , &y , &k);
            int len = y - x + 1;
            int l = SA::rk[x] + 1 , r = n;
            while (l <= r){
    
    
                int md = ((l + r) >> 1);
                if (query_minn(SA::rk[x] + 1 , md) >= len) l = md + 1;
                else r = md - 1;
            }
            int wl , wr;
            wr = r;
            l = 1 , r = SA::rk[x];
            while (l <= r){
    
    
                int md = ((l + r) >> 1);
                if (query_minn(md , SA::rk[x]) >= len) r = md - 1;
                else l = md + 1;
            }
            wl = l - 1;
            if (wr - wl + 1 < k){
    
    
                printf("-1\n");
                continue;
            }
            printf("%d\n" , ask(rt[wl - 1] , rt[wr] , 1 , n , k));
        }
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_35577488/article/details/109037091