小Q与内存

Posted yoyoball

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了小Q与内存相关的知识,希望对你有一定的参考价值。

Portal --> broken qwq

Description

  (这个描述好像怎么都精简不起来啊qwq)

  大概是说你的计算机有1GB的物理内存,按照Byte寻址,其物理地址空间为(0sim 2^{30}-1),然后要支持以下三种操作:

(1)alloc k:申请一段长度为(k) byte的内存,申请成功返回该段内存的标号。分配方式为将当前未分配的地址中最小的(k)个依次分给该内存段(可能不连续),如果当前内存少于(k) byte,执行失败

(2)free i:释放标号为(i)的内存段,该内存段中的物理内存状态被重置为未分配,如果说该标号不存在或者说已经被释放则执行失败

(3)access i,p:计算标号为(i)的内存段的第(p)个byte的物理地址,如果标号不合法或者位置不合法执行失败。(p)(0)开始计算

  数据范围:多组数据,数据组数为(T)(1<=n<=2*10^5,T<=5)

  

Solution

  这题首先有一个难点就是你的语文一定要很好== alloc操作中,不管申请成功与否,都是先开了一个新的内存段的qwq,所以不管有没有成功标号都要加一

  然后我们就可以开始做题了qwq

  这题有两种做法,可以选择用非旋treap或者线段树,虽然说两个算法在实际运行的时候表现都十分优秀但是。。貌似前者的复杂度。。有点问题==(具体我也不是很清楚qwq这个时候应该疯狂膜拜lyy)

  接下来讲一下线段树的做法

  其实这题和某道splay板题有点像【Portal -->】,大体的思路也是我可以用一个点来表示一整个区间,然后只有在要用的时候再将这个点要用的部分给。。切出来就好了

  所以现在先确定一下我们要干的大概是什么:首先我们可以建一棵(0sim 2^{30}-1)的线段树(初始的时候只是一个点,如果一定要建出来的话每个节点对应的区间长度应该是(2)的整数次幂),然后每次alloc我们从这棵线段树中切一部分出来分配到当前内存段下,每次free我们又将某段内存段对应的那一部分给接回大的线段树中,access就直接查找就好了

  所以我们需要实现的是:分离(split)、合并(merge)、查找(query)

  合并的话就是正常的线段树合并即可,没有什么特别的地方

  至于其他的操作,首先先定义一些要维护的值:

  为了方便表示区间,我们考虑给每个节点打上一个(tag),具体含义就是:如果说(tag)(-1),那么说明这个节点的区间已经分裂出左儿子和右儿子了,具体的统计什么的要继续递归处理其左右儿子,否则表示这个区间还没有分裂出左儿子和右儿子,并且这个区间的长度是(2^{tag})

  同时我们用(sz)表示每个节点表示的区间中实际有多少个空置的叶子节点

  至于如何将一个节点存的区间给建出来的话。。考虑实现一个pushdown,如果说当前节点还能分的话那么新建左右儿子,并且左右儿子的(tag)值应该是父亲的(tag)(-1),这里注意一下我们需要区分不能继续往下分的节点和可以继续往下分但是当前没有分的节点(说白了就是两个目前都没有后继可是前者不可以继续往下递归),实现的时候可以一个的左右儿子都设为(-1),另一个都设为(0)

  

  然后我们讲分离操作

  假设我们要将前(k)个位置分离出来(其实也就是前(k)个叶子节点),我们其实相当于在线段树上面找到第(k)个位置,然后把沿路上所有”前面“的部分全部提出来,并且将这些部分与原来大的线段树的连边删掉,所以我们只要一路上经过的节点都复制一个出来(用来链接),然后如果说递归走到左儿子那么不需要进行操作,如果递归走到右儿子说明整个左子树都是应该被提出来的部分,所以左子树直接断开,(k)递归的时候处理就按照找第(k)小处理就好了,注意不管是哪种情况,当前节点的(sz)都要减去(k)(被切掉了)

  接着是查询操作

  查询的话,我们直接在查询的内存段对应的线段树里面找第(k)个位置(从(0)开始数),具体实现有点玄学,因为我们要求这个位置原本的物理地址,也就是这个在原来大的线段树中是第几个叶子节点,所以考虑维护一个(ret)值,表示递归到当前这层,在前面的有多少个区间(这样说有点抽象,看图):

技术分享图片

  然后如果说我们在查找的过程中遇到了一个没有分裂的节点(x)(也就是(tag[x] eq -1)),那么就直接返回(2^{tag[x]}*ret+k)(注意这里的(k)是当前递归传进来的(k)并不是题目查询的原来那个值),如果说一直递归到最后(也就是底层,此时左右儿子都是(-1))没有遇到任何一个没有分裂的节点,那么直接(ret)就是答案了(因为是从(0)开始数的嘛)

  然后就十分愉悦地做完了,时间复杂度的话。。各项操作都是均摊(log)的,总的复杂度(O(nlogn))

  

  代码大概长这个样子

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int N=2*(1e5)+10,SEG=N*100;
int n,T;
namespace Seg{/*{{{*/
    int ch[SEG][2],sz[SEG],rt[N],tag[SEG];//tag>=0 -> full
    int tot,cnt_rt,Rt;
    void init(){cnt_rt=0; tot=1; tag[1]=30; sz[1]=1<<tag[1]; Rt=1;}
    void newrt(){rt[++cnt_rt]=0;}
    int newnode(int tg){
        ch[++tot][0]=ch[tot][1]=0; tag[tot]=tg; sz[tot]=tg>=0?1<<tg:0;
        return tot;
    }
    void pushdown(int x){
        if (tag[x]==-1) return;
        if (tag[x]){
            ch[x][0]=newnode(tag[x]-1);
            ch[x][1]=newnode(tag[x]-1);
        }
        else ch[x][0]=ch[x][1]=-1;
        tag[x]=-1;
    }
    int _merge(int x,int y){
        if (!x||!y) return x+y;
        if (ch[x][0]!=-1){
            ch[x][0]=_merge(ch[x][0],ch[y][0]);
            ch[x][1]=_merge(ch[x][1],ch[y][1]);
        }
        sz[x]+=sz[y];
        return x;
    }
    bool Free(int x){
        if (x>cnt_rt||!rt[x]) return false;
        Rt=_merge(Rt,rt[x]);
        rt[x]=0;
        return true;
    }
    void _split(int x,int &now,int k){
        now=newnode(0); sz[now]=0;
        pushdown(x);
        if (ch[x][0]==-1) return;
        tag[now]=-1; sz[now]=k; 
        sz[x]-=k;
        if (ch[x][0]&&k<sz[ch[x][0]])
            _split(ch[x][0],ch[now][0],k);
        else{
            k-=sz[ch[x][0]];
            ch[now][0]=ch[x][0]; ch[x][0]=0;
            _split(ch[x][1],ch[now][1],k);
        }
    }
    bool alloc(int k){
        newrt();
        if (sz[Rt]<k) return false;
        _split(Rt,rt[cnt_rt],k);
        return true;
    }
    void _query(int x,int k,int &ret){
        if (ch[x][0]==-1) return;
        if (tag[x]!=-1) {ret=ret*(1<<tag[x])+k;return;}
        ret*=2;
        if (ch[x][0]&&k<sz[ch[x][0]]) _query(ch[x][0],k,ret);
        else _query(ch[x][1],k-sz[ch[x][0]],++ret);
    }
    bool access(int x,int k,int &ret){
        if (x>cnt_rt||!rt[x]||k>=sz[rt[x]]) return false;
        ret=0;
        _query(rt[x],k,ret);
        return true;
    }
}/*}}}*/

int main(){
#ifndef ONLINE_JUDGE
    freopen("a.in","r",stdin);
#endif
    int x,p,k,tmp,op;
    scanf("%d",&T);
    for (int o=1;o<=T;++o){
        scanf("%d",&n);
        Seg::init();
        for (int i=1;i<=n;++i){
            scanf("%d",&op);
            if (op==1){
                scanf("%d",&k);
                if (Seg::alloc(k)) printf("ok
");
                else printf("failed
");
            }
            else if (op==2){
                scanf("%d",&x);
                if (Seg::Free(x)) printf("ok
");
                else printf("failed
");
            }
            else{
                scanf("%d%d",&x,&p);
                if (Seg::access(x,p,tmp)) printf("%d
",tmp);
                else printf("failed
");
            }
        }
    }
}

以上是关于小Q与内存的主要内容,如果未能解决你的问题,请参考以下文章

带有 UI 和内存泄漏的保留片段

如何使用模块化代码片段中的LeakCanary检测内存泄漏?

微信小程序代码片段

片段 - 全局视图变量与本地和内部类侦听器和内存泄漏

回归 | js实用代码片段的封装与总结(持续更新中...)

C 中的共享内存代码片段