Problem

Description

\(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\) 列的学生离队。

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

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

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

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

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

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

Input Format

输入共 \(q+1\) 行。

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

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

Output Format

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

Sample

Input:

  1. 2 2 3
  2. 1 1
  3. 2 2
  4. 1 2

Output:

  1. 1
  2. 1
  3. 4

Explanation

Explanation for Input

列队的过程如上图所示,每一行描述了一个事件。 在第一个事件中,编号为 \(1\) 的同学离队,这时空位在第一行第一列。接着所有同学 向左标齐,这时编号为 \(2\) 的同学向左移动一步,空位移动到第一行第二列。然后所有同 学向上标齐,这时编号为 \(4\)的同学向上一步,这时空位移动到第二行第二列。最后编号 为 \(1\) 的同学返回填补到空位中。

Range

测试点编号 $n$ $m$ $q$ 其他约定
$1\sim 6$ $\le 1000$ $\le 1000$ $\le 500$
$7\sim 10$ $\le 5 \times 10^{4}$ $\le 5 \times 10^{4}$
$11,12$ $=1$ $\le 10^{5}$ $\le 10^{5}$ 所有事件 $x=1$
$13,14$ $\le 3\times 10^{5}$ $\le 3\times 10^{5}$
$15,16$ $\le 3 \times 10^{5}$
$17,18$ $\le 10^{5}$ $\le 10^{5}$ $\le 10^{5}$
$19,20$ $\le 3 \times 10^{5}$ $\le 3 \times 10^{5}$ $\le 3 \times 10^{5}$

数据保证每一个事件满足\(1\leq x \leq n,1\leq y \leq m\)

Algorithm

  • 线段树/树状数组,就看你操作骚不骚强不强。如果不会线段树操作,那么平衡树也是很可以的。。。

Notice

  • 线段树的赋值维护仔细点= =

  • 注意编号要开\(long\ long\)

  • 思维一定要清晰,不然会被搞懵的。

Mentality

我觉得这是\(NOIP\)史上最\(shi\)的\(Day2~T3\)= =。\(18\)年的都没这个玩意\(shi\)。

首先看到数据范围,\(3e5\) 是挺坑的。。接着我们可以想到,因为每次出队的人必定都会去到最后一列,那么我们是不是可以维护最后一列,最后只需要输出最后一列所有历史状态的并呢?

当然,并不需要维护一个历史状态,其实我们只需要做到当某一个数向左看齐进入某一行的\(m-1\)列时不删除这个数,只是当做这个数不存在了,也就是让它对后面的数不再产生影响即可。可以理解为把出队元素全部放入一个栈。大致如下:

我们假设如下有五个操作:

OPT1:(2,1)出队

这里我们注意,对于新进入的元素\(3\),我们不应该把它叫做\(3\),而是一个指针 \(->1\),代表它指向总最后一列中的第一个位置,如下:

OPT2:(2,2)出队

同理,我们把 \(9\) 也写成一个指针 \(->3\) :

OPT3:(2,2)出队

OPT4:

OPT5:

对于总最后一列,我们这样处理:

  • Step 0

依次由下往上将指针修改为指向位置的值:

  • Step 1

  • Step 2

由于统计所有出队的人即可,我们输出总最后一列中的\(m+1\) ~ \(m+q\)位即可

虽然关于向左看齐的维护用平衡树可以快乐地做到,但是不会啊。。。

接着想想办法维护这个总的最后一列。我们会发现,最后一列必定由原本的最后一列的元素加上每次离队的元素得到。那么接着考虑一下发现,最后一列里的元素可能会有重复。即原本已经出过队的人再一次出队。这我们可以让新离队补充的位置指向之前的位置,最后输出的时候判断更新即可。那么问题在于,我们如何维护最后一列?

我们可以观察到,用平衡树依次维护每次最后一列的向左看齐是可以做到保证能知道当时出列的那个数处于总最后列的哪个位置。那么线段树怎么做呢?我们考虑为每个数都赋一个权值,如果这个数在当前的第\(m\)列的\(n\)个数中,那么它的权值为\(1\),反之为\(0\)。每次向左看齐,如果一个元素脱离了第\(m\)列,那么它在总最后一列中的权值赋为\(0\),对于下一个进入最后一列的元素赋值为\(1\)。如果我们需要寻找此刻最后一列的第\(k\)个元素,我们只需要在总最后一列中寻找第\(k\)个权值为\(1\)的数。这个通过记录\(sum\)在线段树上查找即可。转换成之前的总最后一列的图,我们可以理解为:权值为1的点为红色,否则为黑色,那么序列中的第\(k\)个元素即为从下到上第\(k\)个红色的点。只不过对于每一行我们是横向维护,所以是从左到右第\(k\)个红色元素为当前第\(k\)个

首先,我们根据询问顺序来看,处理出每次询问后当前最后一列里出去了哪个数并记录在相关行,然后我们就可以知道每次向左看齐离开最后一列的数在总最后一列的位置了。

那么接着我们离线处理询问,以\(x\)为第一关键词,询问为第二关键词排序处理,对于每一行,我们单独进行处理。我们把每次向左看齐进来的数记录它在总序列中的位置。当我们处理一个询问时,就像对最后一列的处理一样维护一个\(01\)的权值串即可找出是在当前这一行第\(k\)个位置,这一行原来的数加上新添的数的总序列中的第几个。如果这个元素是这一行中本来就有的元素,我们直接将它加入总最后一列的相应位置,对于第\(i\)个询问,这个位置显然是\(n+i\)。否则,我们在总最后一列相应位置放一个标记,同时值为这个外来的元素之前本身在最后一列中的所在。

最后输出询问,假设我维护的最后一列为数组\(s\),指向标记为\(book\),那么输出如下:

  1. for(int i=n+1;i<=n+q;i++)
  2. {
  3. if(book[i])
  4. s[i]=s[s[i]];
  5. printf("%lld\n",s[i]);
  6. }

完成!

Code

  1. #include <algorithm>
  2. #include <cstdio>
  3. #include <iostream>
  4. using namespace std;
  5. int n, m, Q, sum[2400001], x, adv[2400001], bj[2400001], L, R;
  6. long long s[600001];
  7. bool book[600001];
  8. struct node {
  9. int x, y, d, next;
  10. } k[300002];
  11. bool cmp(node a, node b) {
  12. if (a.x == b.x) return a.d < b.d;
  13. return a.x < b.x;
  14. }
  15. void pushdown(int o, int l, int r) {
  16. if (adv[o] != -1) {
  17. int mid = (l + r) / 2;
  18. adv[o * 2] = adv[o * 2 + 1] = adv[o];
  19. sum[o * 2] = (mid - l + 1) * adv[o];
  20. sum[o * 2 + 1] = (r - mid) * adv[o];
  21. adv[o] = -1;
  22. }
  23. }
  24. void pushup(int o) { sum[o] = sum[o * 2] + sum[o * 2 + 1]; }
  25. void add(int o, int l, int r) {
  26. if (l >= L && r <= R) {
  27. sum[o] = x * (r - l + 1);
  28. adv[o] = x;
  29. return;
  30. }
  31. pushdown(o, l, r);
  32. int mid = (l + r) / 2;
  33. if (mid >= L) add(o * 2, l, mid);
  34. if (mid < R) add(o * 2 + 1, mid + 1, r);
  35. pushup(o);
  36. }
  37. void query(int o, int l, int r) {
  38. int mid = (l + r) / 2;
  39. if (l == r) {
  40. x = r;
  41. return;
  42. }
  43. pushdown(o, l, r);
  44. if (sum[o * 2] < R) {
  45. R -= sum[o * 2];
  46. query(o * 2 + 1, mid + 1, r);
  47. } else
  48. query(o * 2, l, mid);
  49. pushup(o);
  50. }
  51. void work(int l, int r, int X) {
  52. sum[1] = 0;
  53. adv[1] = 0;
  54. L = 1, R = m;
  55. x = 1;
  56. add(1, 1, m + Q);
  57. int xl[300001];
  58. for (int i = l; i <= r; i++)
  59. xl[i - l] = k[i].next; //即将在询问前加入的r-l+1个外来数的位置指针
  60. for (int i = l; i <= r; i++) {
  61. R = k[i].y;
  62. query(1, 1, m + Q); //查询第k个元素
  63. if (x < m)
  64. s[k[i].d + n] = (long long)(X - 1) * m + x; //队内元素加入总最后一列
  65. else {
  66. book[k[i].d + n] = 1; //标记这个位置是指针
  67. s[k[i].d + n] = xl[x - m]; //指针赋值
  68. }
  69. L = x, R = x;
  70. x = 0;
  71. add(1, 1, m + Q); //权值清零
  72. L = m + i - l + 1, R = m + i - l + 1;
  73. x = 1;
  74. add(1, 1, m + Q); //权值赋1
  75. }
  76. }
  77. int main() {
  78. cin >> n >> m >> Q;
  79. for (int i = 1; i <= max(n, m) + Q; i++) adv[i] = -1;
  80. L = 1, R = n, x = 1;
  81. add(1, 1, n + Q);
  82. for (int i = 1; i <= n; i++)
  83. s[i] = (long long)i * (long long)m; //总最后一列的前m个数
  84. for (int i = 1; i <= Q; i++) {
  85. scanf("%d%d", &k[i].x, &k[i].y);
  86. k[i].d = i;
  87. R = k[i].x;
  88. query(1, 1, n + Q); //查找第k个在序列中的元素
  89. k[i].next = x;
  90. L = x, R = x;
  91. x = 0;
  92. add(1, 1, n + Q); //元素出队,权值清零
  93. L = n + i, R = n + i;
  94. x = 1;
  95. add(1, 1, n + Q); //元素入队,权值为1
  96. }
  97. sort(k + 1, k + Q + 1, cmp); //离线询问
  98. int head = 1;
  99. for (int i = 2; i <= Q + 1; i++)
  100. if (k[i].x != k[i - 1].x) {
  101. work(head, i - 1, k[i - 1].x); //对于每一行单独处理
  102. head = i;
  103. }
  104. for (int i = n + 1; i <= n + Q; i++) {
  105. if (book[i]) s[i] = s[s[i]]; //指针定位
  106. printf("%lld\n", s[i]); //输出
  107. }
  108. }

【NOIP 2017】Day2 T3 列队的更多相关文章

  1. 【NOIP 2013 DAY2 T3】 华容道(spfa)

    题目描述 [问题描述] 小 B 最近迷上了华容道,可是他总是要花很长的时间才能完成一次.于是,他想到用编程来完成华容道:给定一种局面, 华容道是否根本就无法完成,如果能完成, 最少需要多少时间. 小 ...

  2. LUGOU 3959 宝藏 (noip 2017 day2 T2)

    传送门 解题思路 去年noip现在拿来写..思路还是听清楚的,记忆化搜索,f[S]表示现在选了集合S时的最小代价,dis[i]表示达到最优时i这个点的深度.f[S| (1< < i-1) ...

  3. 【NOIP 2015 DAY2 T3】 运输计划 (树链剖分-LCA)

    题目背景 公元 2044 年,人类进入了宇宙纪元. 题目描述 L 国有 n 个星球,还有 n-1 条双向航道,每条航道建立在两个星球之间,这 n-1 条航道连通了 L 国的所有星球. 小 P 掌管一家 ...

  4. NOIP2017 Day2 T3 列队(treap)

    可以直接用treap上大模拟...n+1个treap维护n行的前m-1个点和最后一列. 需要支持删除一个点或者一段区间,而空间并不支持存下所有的点的时候,可以用一个点代替一个区间,记录区间首项的值和区 ...

  5. 「NOIP 2017」列队

    题目大意:给定一个 $n times m$ 的方阵,初始时第 $i$ 行第 $j$ 列的人的编号为 $(i-1) times m + j$,$q$ 次给出 $x,y$,让第 $x$ 行 $y$ 列的人 ...

  6. NOIP 2017 列队 - Splay - 树状数组

    题目传送门 传送点I 传送点II 题目大意 (家喻户晓的题目应该不需要大意) (我之前咋把NOIP 2017打成了NOIP 2018,好绝望) Solution 1 Splay 每行一颗Splay,没 ...

  7. 【游记】NOIP 2017

    时间:2017.11.11~2017.11.12 地点:广东省广州市第六中学 Day1 T1:看到题目,心想这种题目也能放在T1? 这个结论我之前遇到过至少3次,自己也简单证明过.初见是NOIP200 ...

  8. NOIP 2017 解题报告

    ---恢复内容开始--- NOIP 2017 的题真的很难啊,怪不得当年我这个萌新爆零了(当然现在也是萌新)越学越觉得自己什么都不会. 想要成为强者要把这些好题都弄懂弄透 至少现在6道题我都比较陌生 ...

  9. 「雅礼集训 2017 Day2」解题报告

    「雅礼集训 2017 Day2」水箱 我怎么知道这种题目都能构造树形结构. 根据高度构造一棵树,在树上倍增找到最大的小于约束条件高度的隔板,开一个 \(vector\) 记录一下,然后对于每个 \(v ...

随机推荐

  1. 【转】机器学习笔记之(3)——Logistic回归(逻辑斯蒂回归)

    原文链接:https://blog.csdn.net/gwplovekimi/article/details/80288964 本博文为逻辑斯特回归的学习笔记.由于仅仅是学习笔记,水平有限,还望广大读 ...

  2. C# 选项卡控件

    选项卡控件,它提供一系列操作按钮,单击不同的按钮可以在各个页面之间进行切换. 在Windows Form应用程序中,选项卡控件即“TebPage”控件,它公开“TebPage”属性,表示一个由“Tab ...

  3. SpringMVC七种参数绑定简单介绍

    a. 默认支持的类型:        httpServletRequest, httpservletresponse, httpsession, model        看自己需要, 如果需要用就加 ...

  4. 51Nod 1049 最大子段和

    题目链接:https://www.51nod.com/onlineJudge/questionCode.html#!problemId=1049 #include<iostream> #i ...

  5. NoSql Cassandra

    我们为什么要使用NOSQL非关系数据库? 随着互联网web2.0网站的兴起,非关系型的数据库现在成了一个极其热门的新领域,非关系数据库产品的发展非常迅速.而传统的关系数据库在应付web2.0网站,特别 ...

  6. node.js学习(一)

    一.assert assert.deepEqual(actual, expected[, message]) 测试 actual 参数与 expected 参数是否深度相等. 原始值使用相等运算符(= ...

  7. springMVC之一(页面<--->控制器 互相传值,转发和重定向)

    #页面--->控制器1.request:不建议使用2.使用属性传值(建议使用)@RequestParam("name") String username3.使用Bean对象传 ...

  8. Vue小案例 之 商品管理------批量删除与商品数量的调整

    通过索引进行删除,进行测试,是否获取其索引: 测试效果: 测试代码,在vue中定义一个空的数组,以便后面进行数据的绑定: data:{ imgUrl:'../res/images/', imgName ...

  9. 20145212 罗天晨 《网络对抗》Exp3 Advanced 恶意代码伪装技术实践

    恶意代码伪装技术实践 木马化正常软件. 啊哈--原本以为很复杂--然后我看了一下蔡野同学的博客,发现原理竟然如此简单-- 对原先生成病毒的代码稍作修改: 于是--把生成的后门软件改成骗人的名字:这里改 ...

  10. 一、虚拟环境.二、路由配置主页与404.三、2.x路由分发.四、伪静态.五、request对象.六、FBV与CBV.七、文件上传.

    一.虚拟环境 ''' 解决版本共存 1. 用pycharm选择File点击NewProject然后选择virtualenv创建一个纯净环境 2. 打开下载的目录将venv文件夹下的所有文件(纯净的环境 ...