[bzoj3600]没有人的算术

题目大意

定义一种数,要么是0,要么是一个二元组,这个二元组两元都是数。
定义小于是:
1、0<(l,r)
2、如果x<a,那么(x,y)<(a,b)
3、如果x=a,y<b,那么(x,y)<(a,b)
定义等于是:
1、0=0
2、如果x=a,y=b,那么(x,y)=(a,b)
大于与小于类似
现在有一个序列,初始全部为0。
有两种操作:
1、把a[k]修改为(a[l],a[r])
2、询问[l,r]最大的数的坐标,多个最大值输出最小坐标

重量平衡树

对于序列带修改和区间最值询问,我们容易想到利用线段树。
可是如何快速比较两个数的大小呢?
假如我们把所有数都放入平衡树中,中序遍历就是数的大小顺序,那么两个数的大小比较可以直接比较在平衡树中的rank。
但是我们插入平衡树中也要进行数大小比较,这要怎么办?
我们想,我们能不能O(1)比较两个数的大小?
定义一种映射f(x),保证如果x<y,那么f(x)<f(y)
那么假如我们得到了平衡树,让每个节点都对应一个开区间,其中根节点对应(0,1)。
假如一个节点对应开区间是(l,r),mid=(l+r)/2
那么左儿子对应开区间是(l,mid),右儿子对应开区间是(mid,r)
那么定义f(x)=(l+r)/2
节点x上面的f就是开区间的中点
那么因为是开区间,所以一个节点左子树内所有节点的f值小于本身的f值,右子树内所有节点的f值大于本身的f值。
有了f,我们可以o(1)比较两个数的大小。
每次插入新数,因为新数由两个本身存在于平衡树中的数组成,因此可以拿新数和旧数进行比较。
不过我们现在有一个问题,因为平衡树的形态会被改变,所以f值可能会变,那怎么办?
我们可以使用重量平衡树,例如Treap和替罪羊树。
重量平衡树一次调整代价是log n,所以暴力重构这log n个节点的f值即可。
注意,平衡树中不应存在两个大小相同的数,而且要注意0是特殊的。

#include<cstdio>
#include<algorithm>
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
typedef double db;
const int maxn=500000+10;
db f[maxn],L[maxn],R[maxn];
int key[maxn][2],tree[maxn][2],fix[maxn],pos[maxn];
int num[maxn*4];
int i,j,k,l,r,s,t,n,m,tot,root;
char ch;
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*10+ch-'0';
        ch=getchar();
    }
    return x*f;
}
char get(){
    char ch=getchar();
    while (ch!='C'&&ch!='Q') ch=getchar();
    return ch;
}
void left_rotate(int &x){
    int y=tree[x][1];
    tree[x][1]=tree[y][0];
    tree[y][0]=x;
    L[y]=L[x];R[y]=R[x];
    x=y;
    //if (y==root) root=x;
}
void right_rotate(int &x){
    int y=tree[x][0];
    tree[x][0]=tree[y][1];
    tree[y][1]=x;
    L[y]=L[x];R[y]=R[x];
    x=y;
    //if (y==root) root=x;
}
void insert(int &x,int l,int r,db a,db b){
    if (!x){
        x=++tot;
        key[tot][0]=l;
        key[tot][1]=r;
        L[tot]=a;
        R[tot]=b;
        fix[tot]=rand();
        s=x;
        return;
    }
    db c=(a+b)/2;
    int t;
    if (x!=1&&f[l]==f[key[x][0]]&&f[r]==f[key[x][1]]){
        s=x;
        return;
    }
    if (x!=1&&(f[l]<f[key[x][0]]||f[l]==f[key[x][0]]&&f[r]<f[key[x][1]])){
        insert(tree[x][0],l,r,a,c);
        if (fix[tree[x][0]]<fix[x]) right_rotate(x);
    }
    else{
        insert(tree[x][1],l,r,c,b);
        if (fix[tree[x][1]]<fix[x]) left_rotate(x);
    }
}
void rebuild(int x){
    f[x]=(L[x]+R[x])/2;
    if (tree[x][0]){
        L[tree[x][0]]=L[x];
        R[tree[x][0]]=f[x];
        rebuild(tree[x][0]);
    }
    if (tree[x][1]){
        L[tree[x][1]]=f[x];
        R[tree[x][1]]=R[x];
        rebuild(tree[x][1]);
    }   
}
void build(int p,int l,int r){
    if (l==r){
        num[p]=l;
        return;
    }
    int mid=(l+r)/2;
    build(p*2,l,mid);
    build(p*2+1,mid+1,r);
    if (f[pos[num[p*2]]]>=f[pos[num[p*2+1]]]) num[p]=num[p*2];else num[p]=num[p*2+1];
}
void change(int p,int l,int r,int a){
    if (l==r) return;
    int mid=(l+r)/2;
    if (a<=mid) change(p*2,l,mid,a);else change(p*2+1,mid+1,r,a);
    if (f[pos[num[p*2]]]>=f[pos[num[p*2+1]]]) num[p]=num[p*2];else num[p]=num[p*2+1];
}
int query(int p,int l,int r,int a,int b){
    if (l==a&&r==b) return num[p];
    int mid=(l+r)/2;
    if (b<=mid) return query(p*2,l,mid,a,b);
    else if (a>mid) return query(p*2+1,mid+1,r,a,b);
    else{
        int j=query(p*2,l,mid,a,mid),k=query(p*2+1,mid+1,r,mid+1,b);
        if (f[pos[j]]>=f[pos[k]]) return j;else return k;
    }
}
int main(){
    freopen("data.in","r",stdin);freopen("data.out","w",stdout);
    srand(233);
    n=read();m=read();
    root=tot=1;
    L[1]=0;
    R[1]=1;
    f[1]=0.5;
    fix[1]=rand();
    fo(i,1,n) pos[i]=1;
    build(1,1,n);
    fo(i,1,m){
        ch=get();
        if (ch=='C'){
            l=read();r=read();k=read();
            insert(root,pos[l],pos[r],0,1);
            pos[k]=s;
            rebuild(pos[k]);
            change(1,1,n,k);
        }
        else{
            l=read();r=read();
            printf("%d\n",query(1,1,n,l,r));
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值