替罪羊树

本文解析了BZOJ平台上的三道经典题目,包括数据结构优化、线段树应用及平衡树技巧等内容,提供了详细的代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

bzoj3224√

可以将6个操作转化为4个
注意数可能重复所以
找前驱:kth(rank(x)-1)
找后继:kth(rank(x+1))
其中stk是垃圾回收(编号重复利用)

#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;
const int N=200100;
int vis[N],stk[N],ls[N],rs[N];
int sz[N],cnt[N],t[N],v[N];
int n,m,x,k,tot,top,rt,re;
double alpha=0.75;

void dfs(int rt)
{
    if (!rt) return;
    dfs(ls[rt]);
    if (vis[rt])
        t[++tot]=rt; else
        stk[++top]=rt;
    dfs(rs[rt]);
}

void build(int &rt,int l,int r)
{
    if (l>r) {rt=0;return;}
    int mid=(l+r)/2;
    rt=t[mid];
    if (l==r)
    {
        sz[rt]=cnt[rt]=1;
        ls[rt]=rs[rt]=0;
        return;
    }
    build(ls[rt],l,mid-1);
    build(rs[rt],mid+1,r);
    sz[rt]=sz[ls[rt]]+sz[rs[rt]]+1;
    cnt[rt]=cnt[ls[rt]]+cnt[rs[rt]]+1;
}

void rebuild(int &rt)
{
    tot=0;dfs(rt);
    build(rt,1,tot);
}

void ins(int &rt,int val)
{
    if (!rt)
    {
        rt=stk[top--];v[rt]=val;
        sz[rt]=cnt[rt]=vis[rt]=1;
        ls[rt]=rs[rt]=0;
        return;
    }
    sz[rt]++,cnt[rt]++;
    if (val<=v[rt]) 
        ins(ls[rt],val); else
        ins(rs[rt],val);
    if ((double)sz[rt]*alpha>max((double)sz[ls[rt]],(double)sz[rs[rt]]))
    {
        if (re)
        {
            if (ls[rt]==re)
                rebuild(ls[rt]); else
                rebuild(rs[rt]);
            re=0;
        }
    } else re=rt;
}

void del(int &rt,int k)
{
    if (vis[rt] && k==sz[ls[rt]]+1)
    {
        vis[rt]=0;sz[rt]--;
        return;
    }
    sz[rt]--;
    if (k<=sz[ls[rt]]+vis[rt])
        del(ls[rt],k); else
        del(rs[rt],k-sz[ls[rt]]-vis[rt]);
    if ((double)sz[rt]<cnt[rt]*alpha)
        rebuild(rt);
}

int rnk(int x)
{
    int now=rt,res=1;
    while (now)
    {
        if (v[now]>=x)
            now=ls[now]; else
        {
            res+=sz[ls[now]]+vis[now];
            now=rs[now];
        }
    }
    return res;
}

int kth(int x)
{
    int now=rt;
    while (now)
    {
        if (vis[now] && x==sz[ls[now]]+1)
            return v[now];
        if (sz[ls[now]]>=x)
            now=ls[now]; else
            x-=sz[ls[now]]+vis[now],now=rs[now];
    }
}

int main()
{
    freopen("a.in","r",stdin);
    scanf("%d",&n);
    for (int i=400000;i;i--) stk[++top]=i;
    for (int i=1;i<=n;i++)
    {
        scanf("%d%d",&x,&k);
        if (x==1) ins(rt,k);
        if (x==2) del(rt,rnk(k));
        if (x==3) printf("%d\n",rnk(k));
        if (x==4) printf("%d\n",kth(k));
        if (x==5) printf("%d\n",kth(rnk(k)-1));
        if (x==6) printf("%d\n",kth(rnk(k+1)));
    }
}

bzoj3600√

题意简化:

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

分析

注意到题目比较两数的大小事坐标,于是便重载运算符
求的是区间最大值,容易想到线段树。
大概实现:
用平衡树维护这些数,给每个数一个实数值表示其大小
生成一个数(a,b)的时候,由于a,b都是之前出现过的数,
所以我们可以直接在平衡树上插入,返回代表它的实数值
用线段树求区间最大值。by–hzwer

#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;
const int N=500100;
int n,m,l,r,top,cnt,re,rt,k;
int mx[N],pos[N],id[N],sz[N],ls[N],rs[N];
double a[N];
char s[N];

struct node
{
    int x,y;
    friend int operator > (node n,node m)
    {
        if (a[n.x]!=a[m.x])
            return a[n.x]>a[m.x];
        return a[n.y]>a[m.y];
    }
    friend int operator == (node n,node m)
    {
        return a[n.x]==a[m.x] && a[n.y]==a[m.y];
    }
}v[N];

void build(int &rt,int l,int r,double lv,double rv)
{
    if (l>r){rt=0;return;}
    double md=(lv+rv)/2.0;
    int mid=(l+r)/2;
    rt=id[mid];a[rt]=md;
    build(ls[rt],l,mid-1,lv,md);
    build(rs[rt],mid+1,r,md,rv);
    sz[rt]=sz[ls[rt]]+sz[rs[rt]]+1;
}

void dfs(int rt)
{
    if (!rt) return;
    dfs(ls[rt]);
    id[++top]=rt;
    dfs(rs[rt]);
}

void rebuild(int &rt,double l,double r)
{
    top=0;dfs(rt);
    build(rt,1,top,l,r);
}

int ins(int &rt,double l,double r,node val)
{
    double mid=(l+r)/2.0;
    if (!rt)
    {
        rt=++cnt;a[rt]=mid;
        v[rt]=val;sz[rt]=1;
        return rt;
    }
    if (val==v[rt]) return rt; 
    sz[rt]++;int p;
    if (val>v[rt])
        p=ins(rs[rt],mid,r,val); else
        p=ins(ls[rt],l,mid,val); 
    if (sz[rt]*0.75>max(sz[ls[rt]],sz[rs[rt]]))
    {
        if (re)
        {
            if (re==ls[rt])
                rebuild(ls[rt],l,mid); else
                rebuild(rs[rt],mid,r);
            re=0;
        }
    } else re=rt;
    return p;
}

void modify(int rt,int l,int r,int k)
{
    if (l==r){mx[rt]=l;return;}
    int mid=(l+r)/2;
    if (k<=mid)
        modify(2*rt,l,mid,k); else
        modify(2*rt+1,mid+1,r,k);
    int x=mx[2*rt],y=mx[2*rt+1];
    if (a[pos[x]]>=a[pos[y]])
        mx[rt]=x; else mx[rt]=y;
}

int query(int rt,int l,int r,int lv,int rv)
{
    if (l==lv && r==rv) return mx[rt];
    int p=0,t=0,mid=(l+r)/2;
    if (lv<=mid)
    {
        p=query(2*rt,l,mid,lv,min(mid,rv));
        if (a[pos[p]]>a[pos[t]]) t=p;
    }
    if (rv>mid)
    {
        p=query(2*rt+1,mid+1,r,max(lv,mid+1),rv);
        if (a[pos[p]]>a[pos[t]]) t=p;
    }
    return t;
}

int main()
{
    freopen("a.in","r",stdin);
    scanf("%d%d",&n,&m);
    a[0]=-1;ins(rt,0,1,(node){0,0});
    for (int i=1;i<=n;i++) pos[i]=1;
    for (int i=1;i<=n;i++) modify(1,1,n,i);
    for (int i=1;i<=m;i++)
    {
        scanf("%s%d%d",s,&l,&r);
        if (s[0]=='C')
        {
            scanf("%d",&k);
            pos[k]=ins(rt,0,1,(node){pos[l],pos[r]});
            if (re) rebuild(rt,0,1);re=0;
            modify(1,1,n,k); 
        } else printf("%d\n",query(1,1,n,l,r));
    }
}

bzoj3065√

不能用带旋转平衡树的原因:

一般来说平衡树各项操作都是O(logN)的,但是由于外层要维护一个线段树,那么带旋转的平衡树复杂度就难以保证,因为每动一个节点就要在线段树中插入这个节点的子树大小个数的点。(点的深度和子树大小负相关)带旋转的平衡树最坏情况每次都调整某个点到根的路径,而不带旋转的替罪羊树则是调整整棵子树。所以带旋转的平衡树中深度越小的点越可能被调整,替罪羊树中深度越小的点越稳定,因此里层平衡树建议选用替罪羊树。同时注意替罪羊树的重构操作时可以用线段树合并,二分答案时可以找到若干棵线段树一起来二分,这样复杂度可以做到O(NLogNLogN)。

说白了就是:在替罪羊树每个结点放一棵包含该子树所有结点的权值线段树,也就是平衡树套权值线段树
1、插入:在树中找到它相应的位置,在找的过程中将所有的结点的权值线段树插入这个值。
2、查询:找到这个区间包含的所有子树,用它们的根的权值线段树在二分乱搞。
3、修改:在树中找它的位置,在找的过程中将所有的结点的权值线段树删除原值插入新值。
注意:内存紧,开个数组来回收垃圾~

#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;
const int N=101000;
int n,m,pcnt,qcnt,scnt,tot,root,re,ans;
int ls[N],rs[N],a[N],p[N],q[N],s[N*100+5];
int tr[N],sz[N*100],c[N*100][2];
double alpha=0.75;
char ss[5];

void pins(int &rt)
{
    if (scnt>N*100) scnt--;
    s[++scnt]=rt;
    if (c[rt][0]) pins(c[rt][0]);
    if (c[rt][1]) pins(c[rt][1]);
    sz[rt]=0;rt=0;
}

void insert(int &rt,int l,int r,int k,int val)
{
    if (!rt)
    {
        if (!scnt)
            rt=++tot; else
            rt=s[scnt--];
    }
    if (l==r)
    {
        sz[rt]+=val;
        return;
    }
    int mid=(l+r)/2;
    if (k<=mid)
        insert(c[rt][0],l,mid,k,val); else
        insert(c[rt][1],mid+1,r,k,val);
    sz[rt]=sz[c[rt][0]]+sz[c[rt][1]];
    if (!sz[rt]) pins(rt);
}

void build(int &rt,int l,int r)
{
    if (l>r) return;
    if (l==r)
    {
        rt=p[l];
        insert(tr[rt],0,70000,a[rt],1);
        return;
    }
    int mid=(l+r)/2;
    rt=p[mid];
    build(ls[rt],l,mid-1);
    build(rs[rt],mid+1,r);
    for (int i=l;i<=r;i++)
        insert(tr[rt],0,70000,a[p[i]],1);
}

void dfs(int &rt)
{
    if (!rt) return;
    pins(tr[rt]);
    dfs(ls[rt]);
    p[++pcnt]=rt;
    dfs(rs[rt]);
    rt=0;
}

void rebuild(int &rt)
{
    pcnt=0;dfs(rt);
    build(rt,1,pcnt);
}

void ins(int &rt,int k,int val)
{
    if (!rt)
    {
        rt=++n;
        insert(tr[rt],0,70000,val,1);
        a[rt]=val;return;
    }
    insert(tr[rt],0,70000,val,1);
    int tmp=sz[tr[ls[rt]]];
    if (tmp>=k)
        ins(ls[rt],k,val); else
        ins(rs[rt],k-tmp-1,val);
    if ((double)sz[tr[rt]]*alpha>(double)max(sz[tr[ls[rt]]],sz[tr[rs[rt]]]))
    {
        if (re)
        {
            if (re==ls[rt])
                rebuild(ls[rt]); else
                rebuild(rs[rt]);
            re=0;
        }
    } else re=rt;
}

int change(int rt,int k,int val)
{
    insert(tr[rt],0,70000,val,1);
    int tmp=sz[tr[ls[rt]]]+1,t;
    if (tmp==k) t=a[rt],a[rt]=val; else
    if (tmp>k) t=change(ls[rt],k,val); else
        t=change(rs[rt],k-tmp,val);
    insert(tr[rt],0,70000,t,-1);
    return t;
}

void preinit(int rt,int l,int r)
{
    int tmp=sz[tr[ls[rt]]]+1;
    if (l==1 && r==sz[tr[rt]]) 
    {
        p[++pcnt]=tr[rt];
        return;
    }
    if (l<=tmp && tmp<=r) q[++qcnt]=a[rt];
    if (r<tmp) preinit(ls[rt],l,r); else
    if (l>tmp) preinit(rs[rt],l-tmp,r-tmp); else
    {
        if (l<tmp) preinit(ls[rt],l,tmp-1);
        if (r>tmp) preinit(rs[rt],1,r-tmp);
    }
}

int query(int l,int r,int k)
{
    pcnt=qcnt=0;
    preinit(root,l,r);k--;
    int ll=0,rr=70000,mid,tmp;
    while (ll<rr)
    {
        int mid=(ll+rr)/2;tmp=0;
        for (int i=1;i<=pcnt;i++)
            tmp+=sz[c[p[i]][0]];
        for (int i=1;i<=qcnt;i++)
            if (ll<=q[i] && q[i]<=mid)
                tmp++;
        if (tmp>k)
        {
            for (int i=1;i<=pcnt;i++)
                p[i]=c[p[i]][0];
            rr=mid;
        } else
        {
            for (int i=1;i<=pcnt;i++)
                p[i]=c[p[i]][1];
            ll=mid+1;k-=tmp;
        }
    }
    return ll;
}

int main()
{
    freopen("a.in","r",stdin);
    scanf("%d",&n);
    for (int i=1;i<=n;i++)
        scanf("%d",a+i),p[i]=i;
    build(root,1,n);
    scanf("%d",&m);
    int l,r,k;
    for (int i=1;i<=m;i++)
    {
        scanf("%s%d%d",ss,&l,&r);
        l^=ans,r^=ans;
        if (ss[0]=='I')
        {
            ins(root,l-1,r);
            if (re) rebuild(root);
        } else
        if (ss[0]=='M')
            change(root,l,r);else
        {
            scanf("%d",&k);
            k^=ans;ans=query(l,r,k);
            printf("%d\n",ans);
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值