天天看點

P2839 [國家集訓隊]middle(可持久化線段樹+二分)題目題解

題目

傳送門

一個長度為 n n n的序列 a a a,設其排過序之後為 b b b,其中位數定義為 b [ n / 2 ] b[n/2] b[n/2],其中 a , b a,b a,b從 0 0 0開始标号,除法取下整。

給你一個長度為 n n n的序列 s s s。

回答 Q Q Q個這樣的詢問: s s s的左端點在 [ a , b ] [a,b] [a,b]之間,右端點在 [ c , d ] [c,d] [c,d]之間的子序列中,最大的中位數。

其中 a &lt; b &lt; c &lt; d a&lt;b&lt;c&lt;d a<b<c<d。

位置也從 0 0 0開始标号。

我會使用一些方式強制你線上。

題解

尋找中位數有一個通常的套路:

考慮二分中位數,設 x x x是現在二分的數, m i d mid mid是序列的中位數:

将大于 x x x的數設為 1 1 1,小于 x x x的數設為 − 1 -1 −1。

将整個 1 / − 1 1/-1 1/−1序列求和

若 S u m &gt; 0 Sum&gt;0 Sum>0,說明 1 1 1的數量比 − 1 -1 −1多,也就是大于 x x x的數比小于 x x x的數多

說明 x &lt; = m i d x&lt;=mid x<=mid

反之 x &gt; m i d x&gt;mid x>mid

回到本題, [ b + 1 , c − 1 ] [b+1,c-1] [b+1,c−1]為必選區間, [ a , b ] [a,b] [a,b]選字尾, [ c , d ] [c,d] [c,d]選字首。

要使中位數盡可能大,我們要使 S u m Sum Sum盡量大。

因為 [ b + 1 , c − 1 ] [b+1,c-1] [b+1,c−1]固定且 [ a , b ] [a,b] [a,b]字尾 [ c , d ] [c,d] [c,d]字首互不影響,是以在 [ a , b ] [a,b] [a,b]區間我們選擇最大字尾,在 [ c , d ] [c,d] [c,d]區間我們選擇最大字首。

至此,我們已經有具體思路了:

線段樹維護區間最大字首,最大字尾,區間和,每次二分,用線段樹判斷可不可行。

但是,若使用普通線段樹,每次查詢都要重置區間為 1 / − 1 1/-1 1/−1,是以查詢時間複雜度為 O ( n l o g n ) O(nlogn) O(nlogn)

不 T L E TLE TLE才奇怪。

考慮使用可持久化線段樹,把每次二分後的 1 / − 1 1/-1 1/−1序列預處理下來,每次查詢就是查一個曆史版本

這樣每次查詢時間複雜度為 O ( l o g 2 n ) O(log^2n) O(log2n),預處理複雜度為 O ( n l o g n ) O(nlogn) O(nlogn),總複雜度為 O ( n l o g n + q l o g 2 n ) O(nlogn+qlog^2n) O(nlogn+qlog2n)可以 A C AC AC。

#include <bits/stdc++.h>
#define MAXN 20005
using namespace std;
inline int read(){
    int x=0,f=1;
    char ch=getchar();
    while (ch<'0'||ch>'9'){
        if (ch=='-') f=-1;
        ch=getchar();
    }
    while (ch>='0'&&ch<='9'){
        x=(x<<3)+(x<<1)+(ch^'0');
        ch=getchar();
    }
    return x*f;
}
namespace SegmentTree{
    struct node{
        int l,r;
        int lmax,rmax,sum;
    }tree[MAXN*20];
    int tot;
    #define lc tree[i].l
    #define rc tree[i].r
    node operator + (node A,node B){
        node C;
        C.sum=A.sum+B.sum;
        C.lmax=max(A.lmax,A.sum+B.lmax);
        C.rmax=max(B.rmax,A.rmax+B.sum);
        return C;
    }
    void pushup(int i){
        tree[i].sum=tree[lc].sum+tree[rc].sum;
        tree[i].lmax=max(tree[lc].lmax,tree[lc].sum+tree[rc].lmax);
        tree[i].rmax=max(tree[rc].rmax,tree[lc].rmax+tree[rc].sum);
    }
    inline void Value(int i,int val){
        tree[i].sum=tree[i].lmax=tree[i].rmax=val;
    }
    void build(int &i,int L,int R){//一開始都是1
        if (!i) i=++tot;
        Value(i,R-L+1);
        if (L==R){
            return ;
        }
        int mid=(L+R)>>1;
        build(lc,L,mid);
        build(rc,mid+1,R);
        pushup(i);
    }
    void update(int &i,int L,int R,int index){//修改成-1
        tree[++tot]=tree[i],i=tot;//建立節點
        if (L==R) {
            Value(i,-1);
            return ;
        }
        int mid=(L+R)>>1;
        if (index<=mid) update(lc,L,mid,index);
        else update(rc,mid+1,R,index);
        pushup(i);
    }
    node query(int i,int L,int R,int ql,int qr){
        if (ql<=L&&R<=qr){
            return tree[i];
        }
        int mid=(L+R)>>1;
        if (ql>mid) return query(rc,mid+1,R,ql,qr);
        else if (qr<=mid) return query(lc,L,mid,ql,qr);
        else return query(lc,L,mid,ql,qr)+query(rc,mid+1,R,ql,qr);
    }
}
using namespace SegmentTree;
int rt[MAXN];
int a[MAXN],b[MAXN],c[MAXN];//b:id a:原數組
inline bool cmp(int A,int B){
    return a[A]<a[B];
}
inline void discrete(int n){//離散化
    for (register int i=1;i<=n;++i) b[i]=i;
    sort(b+1,b+1+n,cmp);
}
int n;
#define VAR rt[mid],1,n
inline int Check(int mid,int A,int B,int C,int D){//[A,B] [B+1,C-1] [C,D]
    int sum=0;
    if (B+1<=C-1) sum+=query(VAR,B+1,C-1).sum;//這個特判容易漏掉
    sum+=query(VAR,A,B).rmax;
    sum+=query(VAR,C,D).lmax;
    return sum;
}
int p[4];
int main(){
    n=read();
    for (register int i=1;i<=n;++i) a[i]=read();
    discrete(n);
    build(rt[1],1,n);
    for (register int i=2;i<=n;++i){//預處理曆史版本
        rt[i]=rt[i-1];
        update(rt[i],1,n,b[i-1]);
    }
    int last=0;
    int q=read();
    while (q--){
        for (register int i=0;i<4;++i){
            p[i]=(read()+last)%n+1;
        }
        sort(p,p+4);
        int A=p[0],B=p[1],C=p[2],D=p[3];
        int l=1,r=n;
        int ans=0;
        while (l<=r){
            int mid=(l+r)>>1;
            if (Check(mid,A,B,C,D)>=0) {
                ans=a[b[mid]];
                l=mid+1;
            }
            else {
                r=mid-1;
            }
        }
        last=ans;
        printf("%d\n",ans);
    }
}