【洛谷P3960】列队题解

题目链接

题意:

Sylvia 是一个热爱学习的女孩子。

前段时间,Sylvia 参加了学校的军训。众所周知,军训的时候需要站方阵。

Sylvia 所在的方阵中有 n×m 名学生,方阵的行数为 n ,列数为 m 。

为了便于管理,教官在训练开始时,按照从前到后,从左到右的顺序给方阵中的学生从 1 到 n×m 编上了号码(参见后面的样例)。即:初始时,第 i 行第 j 列 的学生的编号是 (i−1)×m+j 。

然而在练习方阵的时候,经常会有学生因为各种各样的事情需要离队。在一天中,一共发生了 q 件这样的离队事件。每一次离队事件可以用数对(x,y)(1≤x≤n,1≤y≤m)描述,表示第 x 行第 y 列的学生离队。

在有学生离队后,队伍中出现了一个空位。为了队伍的整齐,教官会依次下达这样的两条指令:

  1. 向左看齐。这时第一列保持不动,所有学生向左填补空缺。不难发现在这条指令之后,空位在第 x 行第 m列。

  2. 向前看齐。这时第一行保持不动,所有学生向前填补空缺。不难发现在这条指令之后,空位在第 n 行第 m列。

教官规定不能有两个或更多学生同时离队。即在前一个离队的学生归队之后, 下一个学生才能离队。因此在每一个离队的学生要归队时,队伍中有且仅有第 n 行第 m 列一个空位,这时这个学生会自然地填补到这个位置。

因为站方阵真的很无聊,所以 Sylvia 想要计算每一次离队事件中,离队的同学的编号是多少。

注意:每一个同学的编号不会随着离队事件的发生而改变,在发生离队事件后方阵中同学的编号可能是乱序的。

输入格式:

输入共 q+1 行。

第 1 行包含 3 个用空格分隔的正整数 n,m,q,表示方阵大小是 n 行 m 列,一共发生了 q 次事件。

接下来 q 行按照事件发生顺序描述了 q 件事件。每一行是两个整数 x,y,用一个空格分隔,表示这个离队事件中离队的学生当时排在第 x 行第 y 列。

输出格式:

按照事件输入的顺序,每一个事件输出一行一个整数,表示这个离队事件中离队学生的编号。

样例输入:

  1.  

样例输出:

  1.  

时空限制:

每个测试点2s,512MB

数据范围:

n,m,q≤3×105,对于每一对(x,y),满足1≤x≤n且1≤y≤m。

题解:

O(nq)暴力只能拿30分。

所以我们需要用数据结构来维护当前坐标为(x,y)的学生的编号是多少。

我们可以对于每一行和最后一列分别建一棵线段树,那么学生的离队、入队就对应线段树中的删除节点、插入节点的操作。其实我们并不需要真的插入和删除节点(不然就变成平衡树了),我们只需要维护节点在线段树中的前缀和即可,点在线段树中的前缀和就是点所对应的学生在方阵中的位置。

具体怎么操作呢?

每个节点的值,只有1和0两种。一个学生离队时,只需要把该学生对应的节点的值变为0,这样该节点以后的节点的前缀和都会-1。一个节点入队时,把线段树中当前最后一个节点所在位置(lastpos数组)的后一个位置的节点的权值变为1。

但是,这样会炸空间。所以我们需要动态开点!

我们用lazy来保存该区间的第一个点对应的学生的编号(注意,是学生的编号,不是区间的修改量,本题线段树没有区间修改操作)。

又有一个新问题:线段树的区间应该取多大范围呢?对于前n棵线段树,区间范围应该取1~m-1+q,对于最后一颗线段树,区间范围应该取1~n+q,因为最坏情况下,q个修改操作都在同一棵线段树上进行,而每一个修改操作都会使lastpos+1。

这样,时间复杂度O(qlogn),动态开点也节省了大部分空间,本题就可以做了。参考代码如下:

  1. #include<cstdio>
  2. #include<cstring>
  3. #include<algorithm>
  4. #define ll long long
  5. #define maxn 300001
  6. #define maxid 15000000
  7. #define max(a,b) ((a>b)?a:b)
  8. #define min(a,b) ((a<b)?a:b)
  9. using namespace std;
  10. struct segment{//动态开点线段树
  11. int ls,rs,sum;//左子节点编号、右子节点编号、区间和
  12. ll lazy;//初始化及存储叶子结点所代表人的编号用
  13. segment(){
  14. ls=rs=sum=lazy=;
  15. }
  16. }seg[maxid];
  17. int n,m,q,segid=;
  18. inline void maintain(int bh){//用子节点的信息维护该节点的信息
  19. seg[bh].sum=seg[seg[bh].ls].sum+seg[seg[bh].rs].sum;
  20. }
  21. inline void build(int&bh,int L,int R,int l,int r,ll val){//初始建树
  22. if(!bh)bh=++segid;//动态开点
  23. if(l==L&&r==R){//区间完全覆盖
  24. seg[bh].lazy=val;//打上lazy标记
  25. seg[bh].sum=r-l+;//区间和
  26. return;
  27. }
  28. int mid=(L+R)>>;
  29. if(l<=mid)build(seg[bh].ls,L,mid,l,min(mid,r),val);
  30. if(r>mid)build(seg[bh].rs,mid+,R,max(mid+,l),r,val+max(,mid+-l));
  31. maintain(bh);
  32. }
  33. inline void build2(int&bh,int L,int R,int l,int r,ll val){
  34. if(!bh)bh=++segid;
  35. if(l==L&&r==R){
  36. seg[bh].lazy=val;
  37. seg[bh].sum=r-l+;
  38. return;
  39. }
  40. int mid=(L+R)>>;
  41. if(l<=mid)build2(seg[bh].ls,L,mid,l,min(mid,r),val);
  42. if(r>mid)build2(seg[bh].rs,mid+,R,max(mid+,l),r,val+(long long)m*max(,mid+-l));//和build不同的地方
  43. maintain(bh);
  44. }
  45. inline ll qup(int&bh,int L,int R,int rank){//找到排名为rank的节点所代表人的编号并更新sum
  46. if(L==R)return seg[bh].sum=,seg[bh].lazy;//已经到叶子结点,清除该节点(即该节点的值sum=0)
  47. int mid=(L+R)>>;
  48. if(seg[bh].lazy){//动态开点(开bh的两个子节点)并下传标记
  49. seg[bh].ls=++segid;
  50. seg[bh].rs=++segid;
  51. seg[seg[bh].ls].lazy=seg[bh].lazy;
  52. seg[seg[bh].rs].lazy=seg[bh].lazy+mid-L+;
  53. seg[bh].lazy=;
  54. seg[seg[bh].ls].sum=mid-L+;
  55. seg[seg[bh].rs].sum=R-mid;
  56. }
  57. ll ans;
  58. if(rank<=seg[seg[bh].ls].sum)ans=qup(seg[bh].ls,L,mid,rank);
  59. else ans=qup(seg[bh].rs,mid+,R,rank-seg[seg[bh].ls].sum);
  60. maintain(bh);
  61. return ans;
  62. }
  63. inline ll qup2(int&bh,int L,int R,int rank){
  64. if(L==R)return seg[bh].sum=,seg[bh].lazy;
  65. int mid=(L+R)>>;
  66. if(seg[bh].lazy){
  67. seg[bh].ls=++segid;
  68. seg[bh].rs=++segid;
  69. seg[seg[bh].ls].lazy=seg[bh].lazy;
  70. seg[seg[bh].rs].lazy=seg[bh].lazy+(long long)m*(mid-L+);//和qup不同的地方
  71. seg[bh].lazy=;
  72. seg[seg[bh].ls].sum=mid-L+;
  73. seg[seg[bh].rs].sum=R-mid;
  74. }
  75. ll ans;
  76. if(rank<=seg[seg[bh].ls].sum)ans=qup2(seg[bh].ls,L,mid,rank);
  77. else ans=qup2(seg[bh].rs,mid+,R,rank-seg[seg[bh].ls].sum);
  78. maintain(bh);
  79. return ans;
  80. }
  81. int lastpos[maxn];//每棵线段树最后一次插入的位置
  82. inline void add(int&bh,int L,int R,int pos,ll val){
  83. if(!bh)bh=++segid;//动态开点,此时不可能有lazy标记
  84. if(L==R){//已经到叶子结点,添加节点
  85. seg[bh].lazy=val;
  86. seg[bh].sum=;
  87. return;
  88. }
  89. int mid=(L+R)>>;
  90. if(pos<=mid)add(seg[bh].ls,L,mid,pos,val);
  91. else add(seg[bh].rs,mid+,R,pos,val);
  92. maintain(bh);
  93. }
  94. int main(){
  95. scanf("%d%d%d",&n,&m,&q);
  96. segid=n+;
  97. if(m>)for(int i=;i<=n;++i){//建立n棵行线段树
  98. build(i,,m-+q,,m-,(long long)m*(i-)+);//保证第i棵行线段树的根的编号为i
  99. lastpos[i]=m-;//最后一次插入的位置,下次插入就要在该位置的后一个位置插入
  100. }
  101. int lst=n+;//lst是最后一列的线段树的根的编号
  102. build2(lst,,n+q,,n,m);//建立最后一列线段树
  103. lastpos[lst]=n;
  104. for(int i=;i<q;++i){
  105. int x,y;
  106. scanf("%d%d",&x,&y);
  107. if(y<m){
  108. ll bh=qup(x,,m-+q,y);//需要出队的人的编号
  109. printf("%lld\n",bh);
  110. ll bh2=qup2(lst,,n+q,x);//需要从最后一列的线段树移动到行线段树中的人的编号
  111. add(x,,m-+q,++lastpos[x],bh2);
  112. add(lst,,n+q,++lastpos[lst],bh);
  113. }else{
  114. ll bh2=qup2(lst,,n+q,x);//需要出队的人的编号
  115. printf("%lld\n",bh2);
  116. add(lst,,n+q,++lastpos[lst],bh2);
  117. }
  118. }
  119. return ;
  120. }

【洛谷P3960】列队题解的更多相关文章

  1. 洛谷P3960 列队(NOIP2017)(Splay)

    洛谷题目传送门 最弱的Splay...... 暴力模拟30分(NOIP2017实际得分,因为那时连Splay都不会)...... 发现只是一个点从序列里搬到了另一个位置,其它点的相对位置都没变,可以想 ...

  2. 洛谷 P3960 列队 解题报告

    P3960 列队 题目描述 \(Sylvia\)是一个热爱学习的女♂孩子. 前段时间,\(Sylvia\)参加了学校的军训.众所周知,军训的时候需要站方阵. \(Sylvia\)所在的方阵中有\(n ...

  3. 洛谷 P3960 列队

    https://www.luogu.org/problemnew/show/P3960 常数超大的treap #pragma GCC optimize("Ofast") #incl ...

  4. 洛谷P3960 列队 NOIp2017 线段树/树状数组/splay

    正解:动态开点线段树 解题报告: 传送门! 因为最近学主席树的时候顺便get到了动态开点线段树?刚好想起来很久很久以前就想做结果一直麻油做的这题,,,所以就做下好了QAQ 然后说下,这题有很多种方法, ...

  5. 洛谷P3960 列队(动态开节点线段树)

    题意 题目链接 Sol 看不懂splay..,看不懂树状数组... 只会暴力动态开节点线段树 观察之后不难发现,我们对于行和列需要支持的操作都是相同的:找到第\(k\)大的元素并删除,在末尾插入一个元 ...

  6. 洛谷P3960 列队(Splay)

    传送门 感觉自己好久不打数据结构已经完全不会了orz…… 据说正解树状数组?然而并不会 首先考虑一下每一次操作,就是把一个人从这一行中取出并放到行的最后,再从最后一列取出放到列的最后 那么这两种操作其 ...

  7. 洛谷 P3960 列队【线段树】

    用动态开点线段树分别维护每一行和最后一列,线段树的作用是记录被选的点的个数以及查询第k个没被选的点,每次修改,从行里标记被选的点,从最后一列标记向左看齐之后少的点,然后用vector维护行列的新增点 ...

  8. 洛谷P3960 列队 Splay

    其实思路并不算太难,就是代码量相对较大. 我们将一次离队转换为一次删除和两次加入,这样就可以保证空间是动态分配的,最大也不会暴空间. 说实话写之前感觉会很恶心,但是代码量就还好吧,有些细节需要特殊注意 ...

  9. NOIP2017提高组Day2T3 列队 洛谷P3960 线段树

    原文链接https://www.cnblogs.com/zhouzhendong/p/9265380.html 题目传送门 - 洛谷P3960 题目传送门 - LOJ#2319 题目传送门 - Vij ...

随机推荐

  1. Go语言编程读书笔记:Go channel(1)

    Channel是Go语言在语言级别提供的goroutine间的通信方式.我们可以用channel在两个或多个goroutine之间传递消息.channel是进程内的通信方式,因此通过channel传递 ...

  2. Web用户登录---验证码的设计与实现

    在开发中验证码是比较常用到有效防止这种问题对某一个特定注册用户用特定程序暴力破解方式进行不断的登陆尝试的方式. 验证码生成步骤:1.获得随机生成的颜色,画出随机线条生成背景底.2.String cod ...

  3. 基于SDL2实现俄罗斯方块

    俄罗斯方块有多种旋转规则,我这里采用的是SRS.如果要改变旋转规则的话也很方便. SRS: 内容后续补充.. 代码:https://github.com/CknightX/sdl_Tetris 素材来 ...

  4. 2019 年 React 学习路线图(转)

    转自:https://www.infoq.cn/article/AEkiVAiJf25LZmoUe_yc 之前我们已经介绍了2019 年 Vue 学习路线图,而 React 作为当前应用最广泛的前端框 ...

  5. SQL语句 拆分某些字段,一行变多行

    原文链接:http://bbs.csdn.net/topics/310219852 ------------------------------------- -- Author : liangCK ...

  6. Spring:事务

    摘要 本文摘抄了Spring事务相关的一些理论,主要讲述事务的特性.事务的传播行为.事务的隔离规则. 关键词:事务特性,事务传播,事务隔离 一.什么是事务 事务是用来保证数据的完整性和一致性,正如金钱 ...

  7. myhaits if test判断字符串

    判断参数是否为字符串0 <if test='Type=="0"'><!-- 注意单双引号 --> <include refid="selec ...

  8. mycat使用之MySQL单库分表及均分数据

    转载自 https://blog.csdn.net/smilefyx/article/details/72810531 1.首先在Mycat官网下载安装包,这里就以最新的1.6版本为例,下载地址为:  ...

  9. 机器学习入门:Linear Regression与Normal Equation -2017年8月23日22:11:50

    本文会讲到: (1)另一种线性回归方法:Normal Equation: (2)Gradient Descent与Normal Equation的优缺点:   前面我们通过Gradient Desce ...

  10. JavaScript获取扫码枪相关资料

    https://blog.csdn.net/jiongxian1/article/details/78906124 https://blog.csdn.net/jifengdalu/article/d ...