【题解】BZOJ 3600: 没有人的算术——替罪羊树、线段树

题目传送门

题意

具体的自己去上面看吧...反正不是权限题。

简单来说,就是定义了一类新的数,每个数是0或者为 \((x_L, x_R)\) ,同时定义比较大小的方式为:非零数大于零,否则按字典序比较(先比较 \(x_L\) ,相等就比较 \(x_R\) ,递归定义) 。

一开始序列A中元素都是0,然后支持两种操作:

  • C l r k: 将A[k]赋值为(A[l],A[r])。
  • Q l r: 询问A[l],A[l+1]...A[r]中的最大值的编号,若有多个最大值,输出最小的编号。

简要做法

官方题解 orz vfk&hjy96

我们尝试将问题分解。

首先,如果数组元素不是题目定义的乱七八糟的数,而是普通的整数,那么就是简单的线段树操作了。

观察发现,题目中的“小于”满足传递性,所以可以给出一个全序关系

考虑给每个元素一个唯一的编号,编号满足元素之间的大小关系。这样就可以用线段树维护了。

问题来了,如何确定这个编号呢?

一个直接的想法是:维护一个有序表,存储所有出现过的元素,以及它的“左数”和“右数”在表中对应的位置。

每当产生一个新数 \((x_L, x_R)\)由于所有元素的“左数”和“右数”都一定在表中,我们一定可以找到一个位置,将这个新数插入。然后更新表中的信息。

这显然可以使用平衡树来实现。

但是假如按照题意递归地比较“左数”和“右数”的元素本身,时间复杂度就难以保证,怎么办呢?

考虑给每个元素分配一个不连续的编号。像线段树一样,每个节点对应一个实数区间,它的编号为区间的中点。显然,这样产生的编号满足题目中的全序关系,不会冲突。

于是,在每个节点储存它的“左数”和“右数”的指针,这样比较两个元素时,分别比较“左数”和“右数”的编号的大小即可。

问题似乎解决了。但是还有一个小问题:假如使用了基于旋转的平衡树,每次旋转会破坏区间的性质,难以维护。

没有关系,我们还有不基于旋转的平衡树,简单又粗暴好写又高效的基于暴力重构的替罪羊树是非常优秀的一种(非旋转treap常数好大)。

细节

  • 编号可以不用double,而用unsigned long long,主要是出于速度的考虑。
  • 开fread可以跑得更快
  • 某思博选手因为忘了pushup而WA了3次,惨呐

代码

#include <bits/stdc++.h>
using namespace std;
typedef unsigned long long ll;
const int MAXN=500005, MAXB=2e7;
const double al=0.66;
const ll MX=1ULL<<60;
char BUF[MAXB], *cp=BUF;
void rd(int &x){
    x=0;
    while(*cp<'0'||'9'<*cp)cp++;
    while('0'<=*cp&&*cp<='9')x=x*10+*cp++-'0';
}
char rc(){
    while(*cp<'A'||'Z'<*cp)cp++;
    return *cp++;
}
int N, M, L, R, tot=1, ok, top;
struct Node{
    Node *lc, *rc, *l, *r;
    ll a,b,f;
    int sz;
    void up(){sz=1+lc->sz+rc->sz;}
}nd[MAXN], *root, *st[MAXN], *A[MAXN];
void init(){
    nd[0].lc=nd[0].rc=nd; nd->f=0;
    nd[1].l=nd[1].r=nd[1].lc=nd[1].rc=nd;
    nd[1].sz=1; nd[1].a=0; nd[1].b=MX; nd[1].f=MX>>1;
    root=nd+1;
    for(int i=0; i<=N; ++i) A[i]=nd+1;
}
inline int cmp(Node *l1, Node *r1, Node *l2, Node *r2){
    if(l1->f==l2->f) return r1->f==r2->f?0:(r1->f<r2->f?-1:1);
    return l1->f<l2->f?-1:1;
}
void dfs(Node *x){
    if(x==nd) return; 
    dfs(x->lc); st[top++]=x; dfs(x->rc);
}
void bu(Node *&x, int l, int r, ll a, ll b){
    if(l>r){x=nd; return;}
    int mid=l+r>>1; x=st[mid];
    ll f=a+b>>1;
    x->a=a; x->b=b; x->f=f;
    bu(x->lc,l,mid-1,a,f-1);
    bu(x->rc,mid+1,r,f+1,b);
    x->up();
}
void rebu(Node *&x){
    top=0; dfs(x); bu(x,0,top-1,x->a,x->b);
}
void ins(Node *&x, Node *l, Node *r, ll a, ll b, int i){
    ll f=a+b>>1;
    if(x==nd){
        x=&nd[++tot]; x->lc=x->rc=nd; x->sz=1;
        x->l=l; x->r=r; x->a=a; x->b=b; x->f=f;
        A[i]=x; return;
    }
    int t=cmp(l,r,x->l,x->r);
    if(t==0){A[i]=x; return;}
    else if(t<0){
        ins(x->lc,l,r,a,f-1,i); x->up();
        if(!ok&&x->lc->sz>=al*x->sz) ok=1,rebu(x);
    }else{
        ins(x->rc,l,r,f+1,b,i); x->up();
        if(!ok&&x->rc->sz>=al*x->sz) ok=1,rebu(x);
    }
}
inline void chkmx(int &x, int y){
    ll a=A[x]->f, b=A[y]->f;
    if(!x||a<b||a==b&&x>y)x=y;
}
struct Seg{
    int mx[MAXN];
    void pushup(int x){
        chkmx(mx[x],mx[x<<1]);
        chkmx(mx[x],mx[x<<1|1]);
    }
    void upd(int x, int l, int r, int k){
        if(l==r){chkmx(mx[x],k); return;}
        int mid=l+r>>1;
        if(k<=mid) upd(x<<1,l,mid,k);
        else upd(x<<1|1,mid+1,r,k);
        pushup(x);
    }
    void bu(int x, int l, int r){
        if(l==r){mx[x]=l; return;}
        int mid=l+r>>1;
        bu(x<<1,l,mid);
        bu(x<<1|1,mid+1,r);
        pushup(x);
    }
    int gmax(int x, int l, int r){
        if(L<=l&&r<=R) return mx[x];
        int mid=l+r>>1, ret=0;
        if(L<=mid) chkmx(ret,gmax(x<<1,l,mid));
        if(mid<R) chkmx(ret,gmax(x<<1|1,mid+1,r));
        return ret;
    }
}seg;
int main(){
    fread(BUF, 1, MAXB, stdin);
    rd(N),rd(M); init(); seg.bu(1,1,N);
    while(M--){
        if(rc()=='C'){
            int a,b,c; rd(a),rd(b),rd(c); ok=0;
            ins(root,A[a],A[b],0,MX,c);
            seg.upd(1,1,N,c);
        }else{
            rd(L),rd(R);
            printf("%d\n", seg.gmax(1,1,N));
        }
    }
    return 0;
}
posted @ 2017-04-25 17:27 will7101 阅读(...) 评论(...) 编辑 收藏