题目链接:点击查看

题目大意:给定n个内存和m个操作,分别是:

  1. New x:从内存编号1开始向右查找,分配一个长度为x的空间,若找到输出区间的首地址,否则输出Reject New;
  2. Free x:释放包含x在内的连续区间,若释放成功,输出该区间的首地址和尾地址,否则输出Reject Free;
  3. Get x:从内存编号1开始向右查找,查找第x个连续区间内存,若找到输出该区间的首地址,否则输出Reject Get;
  4. Reset:释放所有内存(手动初始化)

题目分析:

先对这个题吐槽一下吧,本来以为昨天晚上做的hotle那个题就已经很恶心了,写的代码又臭又长,结果今天这个题刷新了我对线段树的认知,这个题暂时让他当我遇到过的最恶心的题目应该没问题了。从早晨九点不到一直做到下午一点多,被恶心的不要不要的,都有点怀疑自己了。


对于上述四个操作挨个分析吧:

对于New这个操作,简直和hotle那个题目的那个一模一样啊,就是让求最左端符合条件的区间,我们用区间合并就可以实现,在update函数和pushup函数中维护ll,rr和all三个变量的值,这三个值分别代表从区间左端点开始的最长连续长度,从区间右端点开始的最长连续长度和区间中的最长连续长度,维护好这三个值后再在query函数中依次查找符合条件的区间,若满足向下递归,若得到答案则向上返回答案,都是套路了,一会看代码就好。

对于Free操作,有个难点就是如何找到连续区间的首地址和尾地址,这里我的方法是在线段树的每个节点中,维护两个变量:st和ed,分别记录该节点所属的连续区间中的首地址与尾地址,那么也就引出了我们需要对于每个Free的x求出所对应的节点编号才能随意访问其节点,这里我用的一个getID的函数,类比于点查询的操作向下递归,只不过返回值改成了该点的节点编号,时间复杂度为logn,毕竟是二分查找嘛。。那么现在我们已经求出了该节点的编号,就可以访问他的首地址和尾地址了,那么接下来需要考虑的是如何初始化以及维护这两个变量,因为这个题目中的n个内存都是大于0的,我们可以将其设置为0,即st=ed=0,代表该节点没有位于连续内存中,当用update函数更新一段区间为连续内存是,将这段内存的首地址和尾地址都分别设置为区间的左右端点,等更新的时候用pushdown函数向下传递就好,为什么不用向上传递呢?因为当用update函数更新的时候,遇到符合条件的区间就不会向下递归了,所以这段区间就已经满足条件了,我们只需要将这段区间下面的节点都更新上首尾地址即可。

对于Get操作,网上有一种很麻烦但跑起来很快的方法,我看了半天还是没怎么看懂,大体意思就是在节点中在维护一个size变量,用来记录当前区间中含有多少个连续的内存,这样的话就可以直接对size进行二分查找(也是类似于点查询的一种),找到具体的节点然后返回首地址即可,这里我是看懂了,只不过在free函数里面每次释放节点都需要更新size的值,这里我不太会,加上关于size的传递我也被绕晕了。。感兴趣的话可以去别人家的博客学习一下。关于这个题,想要实现这个操作我选择了用时间付出点代价,用了stl中的set类,主要是因为set可以实现自动排序,每次遍历一边找到第x个数,直接输出即可,而在保存的时候也是直接插入即可,他在内部会自动排序,free函数中释放后也只需要删除即可,其余的操作就不用我们管了,这样Get操作也变得简单易实现了。

最后就剩下了一个Reset操作了,这个操作不用多说了吧,直接用upset更新一下就好了

还有注意一下输出格式

其实用stl一开始还是担心会被卡时间的,比如给出5e5个内存,然后5e5个操作,每个操作都是删除一个数然后增加一个数,感觉很有可能超时,或者是给出5e5个数,每次都Get最后一个,那时间复杂度就变成了至少1e10了,不过还好谢天谢地这个题目没有卡这些点,让本蒟蒻顺利钻空子过了。。


2021.6.27 UPDATE:

考试周不想复习突然看到了这个题目,读了一遍代码发现题目数据水了,把之前写的 O(n*n) 的代码给放过去了。。

其实正解也不难,瓶颈在于:

  1. 维护一个有序序列,包括增加和删除
  2. 快速查询第 k 大的数字

用线段树实现就好了,懒得重构代码了,继续复习数据库去啦

上代码:

#include<iostream>
#include<cstdio>
#include<string>
#include<cstring>
#include<algorithm>
#include<stack>
#include<queue>
#include<map>
#include<sstream>
#include<set>
#include<cmath>
using namespace std;typedef long long LL;const int inf=0x3f3f3f3f;const int N=5e4+100;set<int>vis;set<int>::iterator it;struct Node
{int l,r;//左右区间端点int ll,rr,all;//从左端点开始的最长连续长度,从右端点开始的最长连续长度,区间内最长连续长度int st,ed;//首地址和尾地址int lazy;//懒标记int mid(){return (l+r)>>1;}int cal_len(){return r-l+1;}void update_len(){ll=rr=all=lazy*cal_len();}
}tree[N<<2];void build(int k,int l,int r)
{tree[k].l=l;tree[k].r=r;tree[k].all=tree[k].ll=tree[k].rr=tree[k].cal_len();tree[k].st=tree[k].ed=0;tree[k].lazy=-1;if(l==r)return;int mid=tree[k].mid();build(k<<1,l,mid);build(k<<1|1,mid+1,r);
}void pushup(int k)
{tree[k].ll=tree[k<<1].ll;tree[k].rr=tree[k<<1|1].rr;if(tree[k].ll==tree[k<<1].cal_len())tree[k].ll+=tree[k<<1|1].ll;if(tree[k].rr==tree[k<<1|1].cal_len())tree[k].rr+=tree[k<<1].rr;tree[k].all=max(tree[k<<1].rr+tree[k<<1|1].ll,max(tree[k<<1].all,tree[k<<1|1].all));
}void pushdown(int k)
{tree[k<<1].st=tree[k<<1|1].st=tree[k].st;tree[k<<1].ed=tree[k<<1|1].ed=tree[k].ed;tree[k<<1].lazy=tree[k<<1|1].lazy=tree[k].lazy;tree[k].lazy=-1;tree[k<<1].update_len();tree[k<<1|1].update_len();
} void update(int k,int l,int r,int val,int flag)//k是节点编号,l和r是左右区间,val代表是否被占用,1代表未被占用,0代表被占用、flag和val的意义相似,1代表被占用,0代表未被占用
{if(tree[k].l>r||tree[k].r<l)return;if(tree[k].l>=l&&tree[k].r<=r){tree[k].lazy=val;tree[k].update_len();if(flag){tree[k].st=l;tree[k].ed=r;}else{tree[k].st=tree[k].ed=0;}return;}if(tree[k].lazy!=-1)pushdown(k);update(k<<1,l,r,val,flag);update(k<<1|1,l,r,val,flag);pushup(k);
}int getID(int k,int pos)//通过节点位置得到节点编号的函数
{if(tree[k].l==tree[k].r)return k;if(tree[k].lazy!=-1)pushdown(k);int mid=tree[k].mid();if(mid>=pos)return getID(k<<1,pos);elsereturn getID(k<<1|1,pos);
}int query(int k,int len)//查询可用空间并返回其首地址
{if(tree[k].l==tree[k].r)if(len==1)return tree[k].ll;if(tree[k].lazy!=-1)pushdown(k);if(tree[k<<1].all>=len){return query(k<<1,len);}else if(tree[k<<1].rr+tree[k<<1|1].ll>=len){return tree[k<<1].r-tree[k<<1].rr+1;}else if(tree[k<<1|1].all>=len){return query(k<<1|1,len);}
}int main()
{
//    freopen("input.txt","r",stdin);int n,m;while(scanf("%d%d",&n,&m)!=EOF){vis.clear();//多组输入记得初始化build(1,1,n);char s[10];while(m--){scanf("%s",s);if(s[0]=='N'){int len;scanf("%d",&len);if(len>tree[1].all)cout<<"Reject New"<<endl;else{int temp=query(1,len);printf("New at %d\n",temp);update(1,temp,temp+len-1,0,1);vis.insert(temp);}}else if(s[0]=='R'){cout<<"Reset Now"<<endl;update(1,1,n,1,0);vis.clear();}else if(s[0]=='G'){int x;scanf("%d",&x);int cnt=0;for(it=vis.begin();it!=vis.end();it++){cnt++;if(cnt==x)break;}if(cnt<x)cout<<"Reject Get"<<endl;elseprintf("Get at %d\n",*it);}else if(s[0]=='F'){int x;scanf("%d",&x);int cnt=getID(1,x);if(tree[cnt].st==0)cout<<"Reject Free"<<endl;else{printf("Free from %d to %d\n",tree[cnt].st,tree[cnt].ed);vis.erase(tree[cnt].st);update(1,tree[cnt].st,tree[cnt].ed,1,0);}}}cout<<endl;//记得输出一个回车防止PE}return 0;
}

HDU - 2871 Memory Control(线段树+区间合并)好题!相关推荐

  1. hdu 2871 Memory Control(线段树)

    题目链接:hdu 2871 Memory Control 题目大意:模拟一个内存分配机制. Reset:重置,释放全部空间 New x:申请内存为x的空间,输出左地址 Free x:释放地址x所在的内 ...

  2. HDU - 3397 Sequence operation(线段树+区间合并)

    题目链接:点击查看 题目大意:给定一个初始的数列,然后依次进行m次操作: 0 a b:将闭区间[a,b]内的点都变为0 1 a b:将闭区间[a,b]内的点都变为1 2 a b:将闭区间[a,b]内的 ...

  3. HDU - 1540 Tunnel Warfare(线段树+区间合并)

    题目链接:点击查看 题目大意:给定n个村庄,初始化全部连接为一条直线,需要依次执行m个操作,D表示摧毁第i个村庄的连接,R表示恢复最后一 个被摧毁的村庄的连接,Q表示询问包括本身在内,与第i个村庄相连 ...

  4. HDU 1540 Tunnel Warfare 线段树区间合并

    Tunnel Warfare 题意:D代表破坏村庄,R代表修复最后被破坏的那个村庄,Q代表询问包括x在内的最大连续区间是多少 思路:一个节点的最大连续区间由(左儿子的最大的连续区间,右儿子的最大连续区 ...

  5. hdu 1540 Tunnel Warfare(线段树区间合并)

    hdu 1540 Tunnel Warfare 记录每个节点的最大左连续值.最大右连续值.最大连续值,向上更新的是常规的区间合并处理方式 关键是想到如何去查询,如果查询节点落在左儿子节点的右连续段中, ...

  6. Tunnel Warfare(HDU1540+线段树+区间合并)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1540 题目: 题意:总共有n个村庄,有q次操作,每次操作分为摧毁一座村庄,修复一座村庄,和查询与询问的 ...

  7. CodeForces - 1539F Strange Array(线段树区间合并)

    题目链接:点击查看 题目大意:给出一个长度为 nnn 的序列,规定位置 iii 的贡献是:设 x=a[i]x=a[i]x=a[i],选择一个包含 iii 的区间 [l,r][l,r][l,r],将其中 ...

  8. 2021牛客暑期多校训练营7 xay loves monotonicity 线段树区间合并

    传送门 文章目录 题意: 思路: 题意: 题面挺绕口的,还是看原题比较好. 大概的意思就是让你从给定的区间中选择一个以左端点为起点的一个上升子序列,让后将这些下标存下来,在bbb中将这些位置拿出来后, ...

  9. HDU3308 线段树区间合并

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=3308 ,简单的线段树区间合并. 线段树的区间合并:一般是要求求最长连续区间,在PushUp()函数中实 ...

最新文章

  1. java 数值区间_java 各数据类型数值范围
  2. iOS抓取日志方式(1)
  3. win2003 vps IIS6中添加站点并绑定域名的配置方法
  4. twitter集成第三方登录是窗口一直出现闪退的解决方法
  5. 信息学奥赛一本通 1167:再求f(x,n)
  6. 大数据会如何影响VC领域?
  7. 大数据_MapperReduce_Hbase的优化_RowKey设计原则---Hbase工作笔记0028
  8. python将矩阵顺时针旋转90度_python 二维数组90度旋转的方法
  9. HBase 1.3(NOSQL) 发布,性能大幅提升
  10. [2010-9-8]
  11. net MongoDB安装
  12. 设计模式GOF23之工厂模式01
  13. JavaScript入门详解
  14. WPF监控云台控制组件实现简单方案
  15. 基于卷积神经网络的mnist手写体识别
  16. Advanced IP Scanner - 网络扫描器
  17. 雅虎邮箱,在foxmail 设置
  18. HDS F900装机小结
  19. 障碍期权定价 python_Python二项期权定价
  20. 计算机音乐出山,《出山》音乐

热门文章

  1. MySQL 高级 游标介绍
  2. 地图数据快速渲染------基于传统GIS平台多服务器切片
  3. 手写自己的MyBatis框架-Executor
  4. TCP/IP协议的TCP握手协议
  5. SpringMVC的数据响应-回写数据-返回对象或集合2(应用)
  6. 序列化和反序列化的概述
  7. 上传文件漏洞防御手段
  8. SpringBoot的配置文件-通过@ConfigurationProperties映射数据
  9. RestTemplate的三种使用方式
  10. Oracle之表分区、分区索引(二)