區間k小值(可持久化線段樹)

ruoye123456發表於2024-08-25

題目描述
給定一個序列\(a_1,a_2,\dots,a_n\)\(m\)次操作,每次給定\(l,r,k\),問\(a_l,a_{l+1},\dots,a_r\)中第\(k\)小的值。
輸入
第一行一個正整數\(T(1\leq T\leq 3)\),表示測試資料的數量。

每組資料第一行\(n,m(1\leq n,m\leq 100000)\)

第二行\(n\)個正整數\(a_1,a_2,\dots,a_n(1\leq a_i\leq n)\)

接下來\(m\)行,每行描述一個操作,其中\(1\leq l\leq r\leq n,1\leq k\leq r-l+1\)
輸出
對於每個詢問,輸出一行一個整數,即第\(k\)小的值。
樣例輸入 Copy
1
5 5
1 3 5 4 2
1 3 3
2 4 2
1 5 2
4 4 1
3 5 1
樣例輸出 Copy
5
4
2
4
2

考慮二分答案ans在[l,r]上的<=ans的數和k的關係,用f[i][j]表示前i個數裡j出現的次數,透過可持久化將j作為線段樹的點

然後考慮線段樹上二分去掉一個log

注意不能build,否則會超時,原因尚不明確

#pragma GCC optimize("O3,unroll-loops")
#pragma GCC target("avx2,bmi,bmi2,lzcnt,popcnt")
//如果在不支援 avx2 的平臺上將 avx2 換成 avx 或 SSE 之一
#include<bits/stdc++.h>
using namespace std;
#define x first
#define y second
typedef pair<int,int> PII;
typedef long long ll;
typedef unsigned long long ull;
typedef unsigned int uint;
typedef vector<string> VS;
typedef vector<int> VI;
typedef vector<vector<int>> VVI;
vector<int> vx;
inline int mp(int x) {return upper_bound(vx.begin(),vx.end(),x)-vx.begin();}
inline int log_2(int x) {return 31-__builtin_clz(x);}
inline int popcount(int x) {return __builtin_popcount(x);}
inline int lowbit(int x) {return x&-x;}
const int N = 1e5+10,M = 2e6;
//T[i]裡儲存的是i號線段樹的根節點標號,l,r,val要開到nlogn
int a[N],T[N],l[M],r[M],val[M];
int n,m,tot;
inline void pushup(int p)
{
    val[p] = val[l[p]] + val[r[p]];
}

//對根為x的線段樹區間[a,b]裡的c點修改d
//注意change裡的左右節點編號
int change(int x,int a,int b,int c,int d)
{
    int y = ++tot;
    if(a==b)
    {
        val[y] = val[x] + d;
        return y;
    }
    int M = (a+b)/2;
    if(c<=M) 
    {
        l[y] = change(l[x],a,M,c,d);
        r[y] = r[x];
    }
    else
    {
        l[y] = l[x];
        r[y] = change(r[x],M+1,b,c,d); 
    }
    pushup(y);
    //if(a==4&&b==4) cout<<l[y]<<' '<<r[y]<<'\n';
    return y;
}
//對根為x當前區間為[a,b]的線段樹查詢區間[ql,qr]
int query(int x,int a,int b,int ql,int qr)
{
    if(ql==a&&b==qr) return val[x];
     
    int m = (a+b)/2;
    //注意此處的修改用ql,qr表示詢問區間
    if(qr<=m) return query(l[x],a,m,ql,qr);
    else if(ql>m) return query(r[x],m+1,b,ql,qr);
    else return query(l[x],a,m,ql,m)+query(r[x],m+1,b,m+1,qr);
}
//check(x)計算的是[l,r]中小於等於x的數字個數
//注意呼叫query時使用的是T陣列裡的標號
inline int ask(int x,int y,int k)
{
    int a = 1,b = n;
    while(a < b)
    {
        int m = (a+b)/2;
        int t = val[l[y]] - val[l[x]];
        if(t >= k) x = l[x],y = l[y],b = m;
        else //注意若在右半邊需要把k-=t
        k -= t, x = r[x], y = r[y], a = m+1;
    }
    return a;
}
void solve()
{
    tot = 0;
    cin>>n>>m;
    for(int i=1;i<=n;++i) cin>>a[i];
    
    // T[0] = 1;
    // build(1,n);
     
    for(int i=1;i<=n;++i) T[i] = change(T[i-1],1,n,a[i],1);
     
    while(m--)
    {
        int l,r,k;
        cin>>l>>r>>k;
        cout<<ask(T[l-1],T[r],k)<<'\n';
    }
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    int T;
    cin>>T;
    while(T--)
    {
        solve();
    }
}

相關文章