主席树

Posted shixinyi

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了主席树相关的知识,希望对你有一定的参考价值。

ctsc的D2T1(主席树模板题),大家都半个小时AC了,我因为一个sb bug调了2个多小时……

博主是个大sb。

 

bzoj2653 middle

一个长度为n的序列a,设其排过序之后为b,其中位数定义为b[n/2],其中a,b从0开始标号,除法取下整。

给你一个长度为n的序列s。回答Q个这样的询问:s的左端点在[a,b]之间,右端点在[c,d]之间的子序列中,最大的中位数。

其中a<b<c<d。位置也从0开始标号。强制在线。

如果询问只有一个,我们当然可以二分答案,把$\geq mid$的置为1,其他置为-1,

然后就是求满足左端点在$[a,b]$之间,右端点在$[c,d]$之间的最大权值的子序列的权值是否$\geq 0$

那么对于$[b,c]$之间的所有数,是肯定要选的,那么$[a,b-1]$的最大后缀、$[b,c]$、$[c+1,d]$的最大前缀拼起来就是答案

可以用线段树

对于多组询问,我们肯定不能每次二分一个答案就把所有点权都重置一遍

所以就用主席树,第$i$棵树是二分答案的$mid=i$时查找的线段树,就是$<i$的点权都是-1

//Serene
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<cmath>
#include<vector>
using namespace std;
#define ll long long
#define db double
#define For(i,a,b) for(int i=(a);i<=(b);++i)
#define Rep(i,a,b) for(int i=(a);i>=(b);--i)
#define lc son[pos][0]
#define rc son[pos][1]
const int maxn=2e4+7,maxm=1e7+7;
int n,m,a[maxn],p[maxn],TOT,tot,ans;
vector<int> G[maxn];
 
char cc;ll ff;
template<typename T>void read(T& aa) {
    aa=0;ff=1; cc=getchar();
    while(cc!=‘-‘&&(cc<‘0‘||cc>‘9‘)) cc=getchar();
    if(cc==‘-‘) ff=-1,cc=getchar();
    while(cc>=‘0‘&&cc<=‘9‘) aa=aa*10+cc-‘0‘,cc=getchar();
    aa*=ff;
}
 
struct Node{
    int ld,rd,sum;
    Node(){}
    Node(int ld,int rd,int sum):ld(ld),rd(rd),sum(sum){}
    Node operator + (const Node& b) {
        Node o;
        o.ld=max(ld,sum+b.ld);
        o.rd=max(b.rd,rd+b.sum);
        o.sum=sum+b.sum;
        return o;
    }
}node[maxm];
 
int son[maxm][2];
int ql,qr,qx;
void get_bld(int pos,int l,int r) {
    if(l==r) {
        node[pos]=Node(1,1,1);
        return;
    }
    int mid=(l+r)>>1;
    get_bld(lc=++tot,l,mid);
    get_bld(rc=++tot,mid+1,r);
    node[pos]=node[lc]+node[rc];
}
 
void bld(int& pos,int last,int l,int r) {
    if(!pos) {
        pos=++tot;
        lc=son[last][0];
        rc=son[last][1];
    }   
    if(l==r) {
        node[pos]=Node(0,0,-1);
        return;
    }
    int mid=(l+r)>>1;
    if(qx<=mid) {
        if(lc==son[last][0]) lc=0;
        bld(lc,son[last][0],l,mid);
    }
    else {
        if(rc==son[last][1]) rc=0;
        bld(rc,son[last][1],mid+1,r);
    }
    node[pos]=node[lc]+node[rc];
}
 
Node q(int pos,int l,int r) {
    if(l>=ql&&r<=qr) return node[pos];
    int mid=(l+r)>>1;
    if(qr<=mid) return q(lc,l,mid);
    if(ql>mid) return q(rc,mid+1,r);
    return q(lc,l,mid)+q(rc,mid+1,r);
}
 
bool check(int x,int l1,int r1,int l2,int r2) {
    Node L,O,R; L=R=O=Node(0,0,0);
    ql=l1; qr=r1-1;
    L=q(x,1,n);
    ql=l2+1; qr=r2;
    R=q(x,1,n);
    ql=r1; qr=l2;
    O=q(x,1,n);
    return L.rd+O.sum+R.ld>=0;
}
 
int get_ans(int x,int y,int z,int w) {
    if(x>y) swap(x,y); if(y>z) swap(y,z); if(z>w) swap(z,w);
    if(x>y) swap(x,y); if(y>z) swap(y,z); if(x>y) swap(x,y);
    int l1=x,r1=y,l2=z,r2=w;
//  printf("get_ans:%d~%d,%d~%d\n",l1,r1,l2,r2);
    int l=1,r=TOT,mid;
    if(check(r,l1,r1,l2,r2)) return r;
    while(l<r-1) {
        mid=(l+r)>>1;
        if(check(mid,l1,r1,l2,r2)) l=mid;
        else r=mid;
    }
    return l;
}
 
int main() {
    read(n); int x,y,z,w;
    For(i,1,n) read(a[i]),p[i]=a[i];
    sort(p+1,p+n+1);
    TOT=unique(p+1,p+n+1)-(p+1);
    For(i,1,n) a[i]=lower_bound(p+1,p+TOT+1,a[i])-p;
    For(i,1,n) G[a[i]].push_back(i);
    tot=TOT; get_bld(1,1,n);
    For(i,2,TOT) {
        son[i][0]=son[i-1][0];
        son[i][1]=son[i-1][1];
        node[i]=node[i-1];
        x=G[i-1].size();
        For(j,0,x-1) {
            qx=G[i-1][j];
            bld(i,i-1,1,n);
        }
    }
    read(m);
    For(i,1,m) {
        read(x); read(y); read(z); read(w);
        x=(x+ans)%n+1; y=(y+ans)%n+1;
        z=(z+ans)%n+1; w=(w+ans)%n+1;
        ans=get_ans(x,y,z,w);
        printf("%d\n",ans=p[ans]);
    }
    return 0;
}

 

bzoj3524 Couriers

给一个长度为$n$的序列$a$。$1 \leq a[i] \leq n$。

$m$组询问,每次询问一个区间$[l,r]$,是否存在一个数在$[l,r]$中出现的次数大于$(r-l+1)/2$。如果存在,输出这个数,否则输出0。

一个数,如果满足条件,那么他一定是中位数,所以直接找区间的中位数,然后再查询它出现次数

//Serene
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<cmath>
using namespace std;
#define ll long long
#define db double
#define For(i,a,b) for(int i=(a);i<=(b);++i)
#define Rep(i,a,b) for(int i=(a);i>=(b);--i)
#define lc son[pos][0]
#define rc son[pos][1]
const int maxn=5e5+7,maxm=1e7+7;
int n,m,tot;
 
char cc;ll ff;
template<typename T>void read(T& aa) {
    aa=0;ff=1; cc=getchar();
    while(cc!=‘-‘&&(cc<‘0‘||cc>‘9‘)) cc=getchar();
    if(cc==‘-‘) ff=-1,cc=getchar();
    while(cc>=‘0‘&&cc<=‘9‘) aa=aa*10+cc-‘0‘,cc=getchar();
    aa*=ff;
}
 
int sum[maxm],son[maxm][2],qx,qy;
void bld(int pos,int last,int l,int r) {
    sum[pos]=sum[last]+1;
    if(l==r) return;
    int mid=(l+r)>>1;
    if(qx<=mid) rc=son[last][1],bld(lc=++tot,son[last][0],l,mid);
    else lc=son[last][0],bld(rc=++tot,son[last][1],mid+1,r);
}
 
int q(int ld,int rd,int l,int r) {
    if(l==r) {
        if(sum[rd]-sum[ld]>=qy) return l;
        return 0;
    }
    int mid=(l+r)>>1,x=sum[son[rd][0]]-sum[son[ld][0]];
    if(x>=qx) return q(son[ld][0],son[rd][0],l,mid);
    qx-=x;
    return q(son[ld][1],son[rd][1],mid+1,r);
}
 
int main() {
    read(n); read(m); tot=n;
    For(i,1,n) {
        read(qx); 
        bld(i,i-1,1,n);
    }
    int x,y;
    For(i,1,m) {
        read(x); read(y); qx=qy=(y-x+1)/2+1;
        printf("%d\n",q(x-1,y,1,n));
    }
    return 0;
}

 

bzoj3585 mex

有一个长度为n的数组{a1,a2,...,an}。m次询问,每次询问一个区间内最小没有出现过的自然数。

$n,m \leq 2*10^5$

假如我们二分答案,我们就要求区间内有没有$<mid$的没有出现的数,我们就可以……

这样是两个log的

但是实际上这道题我们可以一个log搞定

第$i$棵线段树(权值线段树),表示的是我们如果只考虑数组的前i个数,那么每个数$x$出现的最大位置$f(x)$是在哪,维护区间最小值

对于询问$(l,r)$,我们在第$r$棵线段树上二分,找到最大的$p$,使得$min(f(1),f(2),...,f(p-1)) \geq l$

//Serene
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<cmath>
using namespace std;
#define ll long long
#define db double
#define For(i,a,b) for(int i=(a);i<=(b);++i)
#define Rep(i,a,b) for(int i=(a);i>=(b);--i)
#define lc son[pos][0]
#define rc son[pos][1]
const int maxn=2e5+7,maxm=1e7+7;
int n,m,W,tot;
 
char cc;ll ff;
template<typename T>void read(T& aa) {
    aa=0;ff=1; cc=getchar();
    while(cc!=‘-‘&&(cc<‘0‘||cc>‘9‘)) cc=getchar();
    if(cc==‘-‘) ff=-1,cc=getchar();
    while(cc>=‘0‘&&cc<=‘9‘) aa=aa*10+cc-‘0‘,cc=getchar();
    aa*=ff;
}
 
int num[maxm],son[maxm][2],ql,qr,qx;
void bld(int pos,int last,int l,int r) {
    if(l==r) {num[pos]=qx;return;}
    int mid=(l+r)>>1;
    if(ql<=mid) rc=son[last][1],bld(lc=++tot,son[last][0],l,mid);
    else lc=son[last][0],bld(rc=++tot,son[last][1],mid+1,r);
    num[pos]=min(num[lc],num[rc]);
}
 
int q(int pos,int l,int r) {
    if(l==r) return l;
    int mid=(l+r)>>1;
    if(num[lc]<qx) return q(lc,l,mid);
    return q(rc,mid+1,r);
}
 
int main() {
    read(n); read(m); W=n+1; tot=n;
    int x,y;
    For(i,1,n) {
        read(x); ++x;
        if(x>n) {
            son[i][0]=son[i-1][0];
            son[i][1]=son[i-1][1];
            continue;
        }
        ql=qr=x; qx=i;
        bld(i,i-1,1,W);
    }
    For(i,1,m) {
        read(x); read(y); qx=x;
        printf("%d\n",q(y,1,W)-1);
    }
    return 0;
}

 

以上是关于主席树的主要内容,如果未能解决你的问题,请参考以下文章

代码源 Div1 - 108#464. 数数(主席树,区间比k小的数的个数)HDU4417

主席树学习记录

Yangk's 静态主席树-模板

bzoj2809 [ APIO2012 ] -- 主席树

主席树

主席树 模板