bzoj 3065 带插入区间K小值 重量平衡treap套主席树

题意:维护一个数列,支持插入,修改,查询区间k小值。

我数据结构还是太弱呀QAQ
看了myy的课件和vfk的题解

treap支持重量平衡,也就是他插入删除影响的节点数都是 O(log) 的。

先说插入,我们先把点插入到最下面,然后往上旋,假设这个点转到祖先k的位置,那么影响节点为 sizek 个,由于这个点的随机权值是 sizek 个点中最小的,这个的概率是 1sizek 的。期望是 O(1) ,一个点有 O(log) 个祖先,因此总期望影响节点数 O(log)

对于删除,由于treap形态是由随机权值决定的,因此删除的数在treap上位置是随机的。由于treap中一个点的子树的期望大小是 O(log) 的。因此是 O(log) 的。

treap插入一个元素旋转的期望次数是 O(1) 的。因为他的随机权值必须比上面的随机权值大才会旋转,这个次数期望 O(1)

前面是几个保证复杂度的东西。

treap每个点代表一个数列中的点。每个点开一棵权值线段树维护子树中的所有点的权值。

由于旋转次数期望 O(1) ,因此可以每次旋转之后直接更新一下信息,不需要最后重构子树里的线段树。
旋转x时,设y是x的父亲,那么旋转后x的线段树和旋转前y的线段树相同,只需更新一下y的线段树。

一开始想用 O(log) 的线段树合并,不过线段树合并不支持内存回收,可能MLE,因此写了一个合并到底的 O(log2)

查询时用前缀和相减的方法,可以得到 log 个线段树或点。在主席树上二分一下时间复杂度 O(log2)

学完splay和fhq treap后觉得普通treap已经完全没有用了,不过treap重量平衡的性质还是非常妙的。

好像常数巨大。。。。附上3.7K代码

#include <bits/stdc++.h>
using namespace std;
#define N 71000
#define M 25000000
#define S 70000
#define which(x) ch[fa[x]][1]==x
#define ls ch[x][0]
#define rs ch[x][1]
struct node
{
    int tp1,tp2,x;
    node(){}
    node(int tp1,int tp2,int x):tp1(tp1),tp2(tp2),x(x){}
}st[N];
int top,n,q,a[N];
char s[11];
struct seg_tree
{
    int ch[M][2],sum[M],cnt;
    queue<int>que;
    int ap()
    {
        if(que.empty())return ++cnt;
        int ret=que.front();que.pop();
        ch[ret][1]=ch[ret][0]=0;sum[ret]=0;
        return ret;
    }
    void insert(int l,int r,int &x,int v,int type)
    {
        if(!x)x=ap();sum[x]+=type;
        if(l==r)return;
        int mid=(l+r)>>1;
        if(v<=mid)insert(l,mid,ch[x][0],v,type);
        else insert(mid+1,r,ch[x][1],v,type);
    }
    void insert(int &x,int v,int type)
    {insert(0,S,x,v,type);}
    int merge(int x,int y)
    {
        if(!x&&!y)return 0;
        int ret=ap();
        ch[ret][0]=merge(ch[x][0],ch[y][0]);
        ch[ret][1]=merge(ch[x][1],ch[y][1]);
        sum[ret]=sum[x]+sum[y];
        return ret;
    }
    void del(int x)
    {
        if(!x)return;
        que.push(x);
        del(ls);del(rs);
    }
    int work(int l,int r,int x)
    {
        if(l==r)return l;
        int t=0,mid=(l+r)>>1;
        for(int i=1;i<=top;i++)
        {
            if(st[i].tp2==1)t+=sum[ch[st[i].x][0]]*st[i].tp1;
            else if(st[i].x<=mid&&st[i].x>=l)t+=st[i].tp1;
        }
        if(t>=x)
        {
            for(int i=1;i<=top;i++)
                if(st[i].tp2==1)st[i].x=ch[st[i].x][0];
            return work(l,mid,x);
        }
        else
        {   
            for(int i=1;i<=top;i++)
                if(st[i].tp2==1)st[i].x=ch[st[i].x][1];
            return work(mid+1,r,x-t);
        }
    }
}tr1;
struct treap
{
    int ch[N][2],rt[N],size[N],rnd[N],root,cnt,fa[N],val[N];
    void pushup(int x)
    {
        size[x]=size[ls]+size[rs]+1;
        rt[x]=tr1.merge(rt[ls],rt[rs]);
        tr1.insert(rt[x],val[x],1);
    }
    int build(int l,int r)
    {
        if(l>r)return 0;
        int mid=(l+r)>>1,x=++cnt;
        val[x]=a[mid];rnd[x]=rand()*rand();
        ch[x][0]=build(l,mid-1);
        ch[x][1]=build(mid+1,r);
        pushup(x);
        return x;
    }
    int rotate(int x)
    {
        int y=fa[x],k=which(x);
        ch[y][k]=ch[x][k^1];ch[x][k^1]=y;
        ch[fa[y]][which(y)]=x;

        fa[x]=fa[y];fa[y]=x;
        fa[ch[y][k]]=y;

        tr1.del(rt[x]);rt[x]=rt[y];
        pushup(y);
        size[x]=size[ls]+size[rs]+1;
        return x;
    }
    int insert(int x,int pos,int v)
    {
        if(!x)
        {
            x=++cnt;tr1.insert(rt[x],v,1);
            size[x]=1;rnd[x]=rand()*rand();
            val[x]=v;
            return x;   
        }
        if(pos<=size[ls])
        {
            tr1.insert(rt[x],v,1);size[x]++;
            ls=insert(ls,pos,v);fa[ls]=x;
            if(rnd[ls]>rnd[x])x=rotate(ls);
        }
        else
        {
            tr1.insert(rt[x],v,1);size[x]++;
            rs=insert(rs,pos-size[ls]-1,v);fa[rs]=x;
            if(rnd[rs]>rnd[x])x=rotate(rs);
        }
        return x;
    }
    int change(int x,int pos,int v)
    {
        if(size[ls]+1==pos)
        {
            tr1.insert(rt[x],val[x],-1);
            tr1.insert(rt[x],v,1);
            return x;
        }
        int t;
        if(size[ls]>=pos)t=change(ls,pos,v);
        else t=change(rs,pos-size[ls]-1,v);
        tr1.insert(rt[x],val[t],-1);
        tr1.insert(rt[x],v,1);
        return t;
    }
    void find(int x,int pos,int tp)
    {
        if(!x||!pos)return;
        if(size[x]==pos)
            st[++top]=node(tp,1,rt[x]);
        else if(size[ls]>=pos)
            find(ls,pos,tp);
        else
        {
            st[++top]=node(tp,1,rt[ls]);
            st[++top]=node(tp,0,val[x]);
            find(rs,pos-size[ls]-1,tp);
        }
    }
}tr2;
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]);
    tr2.root=tr2.build(1,n);
    scanf("%d",&q);
    for(int x,y,k,val,ans=0;q--;)
    {
        scanf("%s",s);
        if(s[0]=='Q')
        {
            scanf("%d%d%d",&x,&y,&k);
            x^=ans;y^=ans;k^=ans;
            top=0;
            tr2.find(tr2.root,x-1,-1);
            tr2.find(tr2.root,y,1);
            ans=tr1.work(0,S,k);
            printf("%d\n",ans);
        }
        if(s[0]=='M')
        {
            scanf("%d%d",&x,&val);
            x^=ans;val^=ans;
            int t=tr2.change(tr2.root,x,val);
            tr2.val[t]=val;
        }
        if(s[0]=='I')
        {
            scanf("%d%d",&x,&val);
            x^=ans;val^=ans;
            tr2.root=tr2.insert(tr2.root,x-1,val);
        }
    }
    return 0;
}   

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
题目描述 有一个 $n$ 个点的棋盘,每个点上有一个数字 $a_i$,你需要从 $(1,1)$ 走到 $(n,n)$,每次只能往右或往下走,每个格子只能经过一次,路径上的数字和为 $S$。定义一个点 $(x,y)$ 的权值为 $a_x+a_y$,求所有满足条件的路径中,所有点的权值和的最小值。 输入格式 第一行一个整数 $n$。 接下来 $n$ 行,每行 $n$ 个整数,表示棋盘上每个点的数字。 输出格式 输出一个整数,表示所有满足条件的路径中,所有点的权值和的最小值。 数据范围 $1\leq n\leq 300$ 输入样例 3 1 2 3 4 5 6 7 8 9 输出样例 25 算法1 (树形dp) $O(n^3)$ 我们可以先将所有点的权值求出来,然后将其看作是一个有权值的图,问题就转化为了在这个图中求从 $(1,1)$ 到 $(n,n)$ 的所有路径中,所有点的权值和的最小值。 我们可以使用树形dp来解决这个问题,具体来说,我们可以将这个图看作是一棵树,每个点的父节点是它的前驱或者后继,然后我们从根节点开始,依次向下遍历,对于每个节点,我们可以考虑它的两个儿子,如果它的两个儿子都被遍历过了,那么我们就可以计算出从它的左儿子到它的右儿子的路径中,所有点的权值和的最小值,然后再将这个值加上当前节点的权值,就可以得到从根节点到当前节点的路径中,所有点的权值和的最小值。 时间复杂度 树形dp的时间复杂度是 $O(n^3)$。 C++ 代码 算法2 (动态规划) $O(n^3)$ 我们可以使用动态规划来解决这个问题,具体来说,我们可以定义 $f(i,j,s)$ 表示从 $(1,1)$ 到 $(i,j)$ 的所有路径中,所有点的权值和为 $s$ 的最小值,那么我们就可以得到如下的状态转移方程: $$ f(i,j,s)=\min\{f(i-1,j,s-a_{i,j}),f(i,j-1,s-a_{i,j})\} $$ 其中 $a_{i,j}$ 表示点 $(i,j)$ 的权值。 时间复杂度 动态规划的时间复杂度是 $O(n^3)$。 C++ 代码

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值