【洛谷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 列。

输出格式:

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

样例输入:

2 2 3
1 1
2 2
1 2

样例输出:

1
1
4

时空限制:

每个测试点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=0;
 15     }
 16 }seg[maxid];
 17 int n,m,q,segid=0;
 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+1;//区间和
 26         return;
 27     }
 28     int mid=(L+R)>>1;
 29     if(l<=mid)build(seg[bh].ls,L,mid,l,min(mid,r),val);
 30     if(r>mid)build(seg[bh].rs,mid+1,R,max(mid+1,l),r,val+max(0,mid+1-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+1;
 38         return;
 39     }
 40     int mid=(L+R)>>1;
 41     if(l<=mid)build2(seg[bh].ls,L,mid,l,min(mid,r),val);
 42     if(r>mid)build2(seg[bh].rs,mid+1,R,max(mid+1,l),r,val+(long long)m*max(0,mid+1-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=0,seg[bh].lazy;//已经到叶子结点,清除该节点(即该节点的值sum=0)
 47     int mid=(L+R)>>1;
 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+1;
 53         seg[bh].lazy=0;
 54         seg[seg[bh].ls].sum=mid-L+1;
 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+1,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=0,seg[bh].lazy;
 65     int mid=(L+R)>>1;
 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+1);//和qup不同的地方
 71         seg[bh].lazy=0;
 72         seg[seg[bh].ls].sum=mid-L+1;
 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+1,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=1;
 87         return;
 88     }
 89     int mid=(L+R)>>1;
 90     if(pos<=mid)add(seg[bh].ls,L,mid,pos,val);
 91     else add(seg[bh].rs,mid+1,R,pos,val);
 92     maintain(bh);
 93 }
 94 int main(){
 95     scanf("%d%d%d",&n,&m,&q);
 96     segid=n+1;
 97     if(m>1)for(int i=1;i<=n;++i){//建立n棵行线段树
 98         build(i,1,m-1+q,1,m-1,(long long)m*(i-1)+1);//保证第i棵行线段树的根的编号为i
 99         lastpos[i]=m-1;//最后一次插入的位置,下次插入就要在该位置的后一个位置插入
100     }
101     int lst=n+1;//lst是最后一列的线段树的根的编号
102     build2(lst,1,n+q,1,n,m);//建立最后一列线段树
103     lastpos[lst]=n;
104     for(int i=0;i<q;++i){
105         int x,y;
106         scanf("%d%d",&x,&y);
107         if(y<m){
108             ll bh=qup(x,1,m-1+q,y);//需要出队的人的编号
109             printf("%lld\n",bh);
110             ll bh2=qup2(lst,1,n+q,x);//需要从最后一列的线段树移动到行线段树中的人的编号
111             add(x,1,m-1+q,++lastpos[x],bh2);
112             add(lst,1,n+q,++lastpos[lst],bh);
113         }else{
114             ll bh2=qup2(lst,1,n+q,x);//需要出队的人的编号
115             printf("%lld\n",bh2);
116             add(lst,1,n+q,++lastpos[lst],bh2);
117         }
118     }
119     return 0;
120 }

转载于:https://www.cnblogs.com/AndyGamma/p/9848178.html

【洛谷P3960】列队题解相关推荐

  1. 洛谷P3960 列队(Splay)

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

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

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

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

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

  4. 洛谷P3960 列队【Splay】

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

  5. 洛谷P1816 忠诚 题解

    洛谷P1816 忠诚 题解 题目描述 老管家是一个聪明能干的人.他为财主工作了整整10年,财主为了让自已账目更加清楚.要求管家每天记k次账,由于管家聪明能干,因而管家总是让财主十分满意.但是由于一些人 ...

  6. 线性存储的最短平均检索时间(洛谷P1253题题解,Java语言描述)

    题目要求 P1253题目链接 分析 很像 ~洛谷P1223题题解~,也是一种类似SJF的贪心法. 排个序,由于两个不大于10000的数,乘起来还是int,就使用int属性吧. 数据量小,所以Scann ...

  7. 队列模拟约瑟夫问题(洛谷P1996题题解,Java语言描述)

    题目要求 P1996题目链接 分析 以前就研究过"约瑟夫环"问题: <单循环链表求解约瑟夫环问题(Java语言描述)> <杀人游戏~约瑟夫环(洛谷P1145题题解 ...

  8. 洛谷P2108学英语题解

    来我的博客里拥有更好的阅读体验:https://yyxi.ml/2020/07/23/luogu-p2108-xueyingyu/ 洛谷P2108学英语题解(c++) 题目描述 代码.思路 踩过的坑 ...

  9. 洛谷 P1077 摆花 题解

    洛谷 P1077 摆花 题解 洛谷 P1077 题目 小明的花店新开张,为了吸引顾客,他想在花店的门口摆上一排花,共mmm盆.通过调查顾客的喜好,小明列出了顾客最喜欢的nnn种花,从1到nnn标号.为 ...

最新文章

  1. vue配置文件读取_Vue+Spring Boot简单用户登录Demo实现
  2. Android Gradle查询器
  3. Android 7.0 Gallery图库源码分析2 - 分析启动流程
  4. 语言题库体型判断问题_1000道Python题库系列分享20(43道填空与判断题)
  5. 01.微服务系列介绍
  6. 面试风云录(01) - 怎样回答这两个问题?
  7. Vue-cli 项目打包布署(简单清晰)
  8. BZOJ 3038 上帝造题的七分钟2
  9. 在没有插件的情况下为Chrome设置Proxy
  10. Python批量提取Excel文件中的图片
  11. vue中进度条写法_vue实现简单loading进度条
  12. Python 之父从 Dropbox 退
  13. WPF管理系统自定义分页控件 - WPF特工队内部资料
  14. 如何在Premiere Pro 中使用动态图形模板
  15. 《Word 排版艺术》一书的人到此交流
  16. Windows XP虚拟机安装全过程(VMware)
  17. 如何网络监测其他计算机关闭445端口,关闭445端口方法 包括XP win7和win10系统(超详细)...
  18. EMV规范(五)——脱机数据认证
  19. hadoop可以解决什么问题_在家艾灸可以解决这些问题
  20. Linux服务器tomcat正常启动,但是通过IP不能访问web

热门文章

  1. YTU2018级每周训练-动态规划1
  2. linux:解压命令
  3. 新概念第三册背诵: Lesson 2 - Thirteen equals one
  4. iOS 获取连接的WiFi和Mac地址
  5. android 小视频 应用,短视频应用专题 - 只给你好看的短视频合集 - Android 应用 - 【最美应用】...
  6. App 被拒后或被下架 向Apple获取帮助或申诉渠道汇总
  7. [免费]织梦xml地图生成插件
  8. i 春秋CTF题目 百度杯 9月场 再见CMS Upload 复现
  9. 洛谷 P2357 守墓人(树状数组)
  10. android小米手机拍照功能介绍,小米手机使用手册