• BZOJ 3600: 没有人的算术


    传送门

    骚题一道,给每个"数"一个实数值 A 来表示它们的相对大小关系 $Ain (0,1)$

    考虑如何维护它们之间的相对大小关系

    有插入操作

    考虑一颗二叉查找树,每个节点维护它子树的最小值$A_{min}$,最大值$A_{max}$,它本身的实数$frac{A_{min}+A_{max}}{2}$和它本身的"数"

    那么儿子维护的值域就分别是$(A_{min},frac{A_{min}+A_{max}}{2})$ 和 $(frac{A_{min}+A_{max}}{2},A_{max})$

    那么插入就直接dfs下去,通过比较"数"来找到对应位置

    但是如果树退化成一条链,会产生很大的精度误差而且复杂度也不行,所以考虑平衡树

    树高只有$log_n$,每次深一层值域减半,所以叶子值域大小就是$frac{1}{2^{log_n}}=frac{1}{n}$,用double就够了

    但是发现如果是经典的 Splay

    每次旋转时都会把整颗子树的数据修改,显然是不可以的

    这时我们就要用替罪羊树了

    考虑替罪羊树的做法,插入时不会修改父亲啊什么的

    不平衡的话直接拍扁暴力重构,复杂度不会证明,反正快的一批

    发现我们在暴力重构的时候顺便维护一下数据也丝毫不会影响到复杂度

    所以就选用替罪羊树维护这些东西

    但是又有一个问题了,怎么求区间最值呢?

    我们的平衡树维护的是值域...

    那么再码一个线段树来求区间最大的数的下标就好了,线段树节点数组记为 $t$

    具体来说就是记一个 $pos[i]$ 表示区间上位置 i 的数在平衡树上的节点编号

    然后线段树上合并时就取 $A[ pos[ t[left\_child] ] ]$ 和 $A[ pos[ t[right\_child] ] ]$ 中较大的 t

    代码参考 hzwer 

    代码有注释

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cmath>
    #include<cstring>
    #include<vector>
    using namespace std;
    typedef long long ll;
    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<<1)+(x<<3)+(ch^48); ch=getchar(); }
        return x*f;
    }
    const int N=1e6+7;
    const double alph=0.75;//替罪羊树的常数
    double A[N];
    int t[N],pos[N],q[N],las;
    //t是和pos文章中有解释,q是临时存当前拍平的节点,las是拍平节点总数
    struct dat//即题中的"数"
    {
        int l,r;
        inline bool operator < (const dat &a) const {
            if(A[l]<A[a.l]) return 1;
            if(A[l]==A[a.l]&&A[r]<A[a.r]) return 1;
            return 0;
        }
        inline bool operator == (const dat &a) const {
            return A[l]==A[a.l]&&A[r]==A[a.r];
        }//重载一下比较符
    };
    dat val[N];//存节点的"数"
    int sz[N],ch[N][2],tot,P;
    //分别为节点子树大小,左右儿子,节点总数,以及当前要拍平的子树的根节点编号
    void dfs(int x)//dfs拍平
    {
        if(ch[x][0]) dfs(ch[x][0]);
        q[++las]=x;
        if(ch[x][1]) dfs(ch[x][1]);
    }
    void build(int &x,int l,int r,double lv,double rv)//拍平后当然要重建啦
    {
        int mid=l+r>>1; double mv=(lv+rv)/2;
        x=q[mid]; A[x]=mv;//当前节点维护的值
        if(mid>l) build(ch[x][0],l,mid-1,lv,mv);//这里不是线段树,mid要减1
        else ch[x][0]=0;//记得如果没有儿子要赋为0
        if(mid<r) build(ch[x][1],mid+1,r,mv,rv);
        else ch[x][1]=0;//同上
        sz[x]=sz[ch[x][0]]+sz[ch[x][1]]+1;//更新sz
    }
    void rebuild(int &x,double lv,double rv)//重建x的子树
    {
        las=0; dfs(x);
        build(x,1,las,lv,rv);
    }
    int ins(int &x,double lv,double rv,dat v)//插入一个"数"v,并返回它的节点编号
    {
        double mv=(lv+rv)/2;//节点本身的值
        if(!x) { x=++tot; A[x]=mv; val[x]=v; sz[x]=1; return x; }//如果没有就新建并返回
        if(val[x]==v) return x;//不然直接返回编号
        int res; sz[x]++;//否则x的子树大小会+1
        if(v<val[x]) res=ins(ch[x][0],lv,mv,v);//找到位置插入
        else res=ins(ch[x][1],mv,rv,v);
        if( sz[x]*alph > max(sz[ch[x][0]],sz[ch[x][1]]) )//替罪羊树核心操作,如果不平衡就拍平重建
        {
            if(P)//如果x平衡但是它的一个儿子不平衡
            {
                if(ch[x][0]==P) rebuild(ch[x][0],lv,mv);
                else rebuild(ch[x][1],mv,rv);
                P=0;//就找到它的不平衡的儿子并拍平
            }
        }
        else P=x;//不平衡就更新下P,儿子不用管,反正等等还是要拍平
        return res;
    }
    //以下线段树操作
    void change(int o,int l,int r,int p)//更新一下线段树
    {
        if(l==r) { t[o]=l; return; }
        int mid=l+r>>1;
        if(p<=mid) change(o<<1,l,mid,p);//找到位置
        else change(o<<1|1,mid+1,r,p);
        if( A[pos[t[o<<1]]] >= A[pos[t[o<<1|1]]] ) t[o]=t[o<<1];
        else t[o]=t[o<<1|1];//更新t
    }
    int query(int o,int l,int r,int ql,int qr)//询问区间最大值
    {
        if(l>=ql&&r<=qr) return t[o];
        int mid=l+r>>1,res=0,t;
        if(ql<=mid) res=query(o<<1,l,mid,ql,qr);
        if(qr>mid)
        {
            t=query(o<<1|1,mid+1,r,ql,qr);
            if(A[pos[t]]>A[pos[res]]) res=t;//更新res
        }
        return res;
    }
     
    int n,m,rt;
    int main()
    {
        //freopen("3600.in","r",stdin);
        //freopen("3600.out","w",stdout);
        n=read(); m=read(); A[0]=-1;
        ins(rt,0,1,(dat){0,0});
        for(int i=1;i<=n;i++) pos[i]=1;//初始大家值一样
        for(int i=1;i<=n;i++) change(1,1,n,i);//初始先更新线段树
        int a,b,c; char s[5];
        while(m--)
        {
            scanf("%s",s); a=read(),b=read();
            if(s[0]=='C')
            {
                c=read();
                pos[c]=ins(rt,0,1,(dat){pos[a],pos[b]});//更新pos
                if(P) rebuild(rt,0,1); P=0;//别忘了整颗树的平衡状态还没判过
                change(1,1,n,c);//此时有位置变化了,更新一下线段树
            }
            else printf("%d
    ",query(1,1,n,a,b));//询问就直接输出
        }
        return 0;
    }
  • 相关阅读:
    JAVA 单例模式
    CodeForces Round #563 Div.2
    拓扑排序 JAVA
    初识 Dubbo
    CodeForces Round #567 Div.2
    Educational Codeforces Round 65 (Rated for Div. 2)
    最短路径问题
    C++使用fixed和precision控制小数和有效位数的输出以及setw()设置输出宽度
    poj3684(弹性碰撞模型)
    集合的整数表示
  • 原文地址:https://www.cnblogs.com/LLTYYC/p/10163022.html
Copyright © 2020-2023  润新知