首先可以观察到这样一个事实,如果 \((x, y)\) 出队,那么只会影响 \(x\) 这一行,以及最后一列的排布。并且可以发现,每次一个人出队,总会对最后一列有影响,因此我们可能需要将最后一列单独拿出来维护。让我们来想一想,什么东西可以支持删除一个数,插入一个数,查询排名为第几的数,显然 \(Splay\) 可以完成这样一个事情。于是一个想法呼之欲出,对于每一行以及最后一列我们开一颗 \(Splay\),每次取出第 \(x\) 行第 \(y\) 个元素插入到最后一列最后(赋一个大权值),将最后一列第 \(x\) 个元素插入到第 \(x\) 行的最后一个位置(赋一个大权值)。可以发现这样虽然每次修改复杂度是 \(O(\log n)\) 的,但由于每行都需要开一颗 \(Splay\),因此空间复杂度是 \(O(nm)\) 的,因此我们还需另辟蹊径。

我们可以发现上面这个做法的问题在于我们维护了所有人的变化情况,但实际上有很多人的位置是没有变化的,还是留在他原来的哪一行,变化了行数的人只有 \(q\) 个,那么我们能否只维护这些变化的人来完成我们想要的目的呢?可以发现这是可以的,同样的对于最后一列直接维护一颗 \(Splay\) 这是可以的,对于第 \(x\) 行,维护一颗 \(Splay\) 存储原来在这一行第 \(1 \sim m - 1\) 列的人有那些人出列(以列数为关键字),再额外维护一颗 \(Splay\) 表示这一行由于出队进入了那些新人(按照插入顺序为关键字),还维护一个 \(d_x\) 表示第 \(x\) 行前 \(1 \sim m - 1\) 个人中出列了几个人。那么我们怎么找到当前 \((x, y)\) 是谁呢?分三种情况讨论,如果 \(y = m\) 那么直接取出最后一列第 \(x\) 名插到最后即可;如果 \(m - 1 - d_x < y\),显然这个人会出现在这进入这一行的新人当中,只需查询维护新人的 \(Splay\) 的第 \(y - (m - 1 - d_x)\) 个人即可;最后一种情况 \(m - 1 - d_x \le y\) 那么这个人一定会是原先这一行中的一个人,可以发现这些出列的人越往后前面留在原位的人会越来越多,因此我们可以直接对这些出列的人进行二分,找到 \(x - r_x < y\) (其中 \(x\) 为出列元素的列数,\(r_x\) 表示列数不大于 \(x\) 的位置里出列了多少个元素)的最后一个位置,这个操作可以直接在 \(Splay\) 上完成,于是总复杂度 \(O((q + n) \log n)\)。代码细节很多,具体看注释。

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define rep(i, l, r) for(int i = l; i <= r; ++i)
typedef long long ll;
const int N = 600000 + 5;
const int M = 4000000 + 5;
const int inf = 1000000000;
ll ans, p[M];
int n, m, q, x, y, la, tot, tmp, s[M], d[N], l[N], rt[N], fa[M], val[M], ch[M][2];
int read(){
char c; int x = 0, f = 1;
c = getchar();
while(c > '9' || c < '0'){ if(c == '-') f = -1; c = getchar();}
while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
return x * f;
}
int which(int x){
return (ch[fa[x]][1] == x);
}
void update(int x){
s[x] = s[ch[x][0]] + s[ch[x][1]] + 1;
}
void rotate(int x){
int y = fa[x], z = fa[y], k = which(x), w = ch[x][k ^ 1];
fa[w] = y, ch[y][k] = w;
fa[x] = z, ch[z][which(y)] = x;
fa[y] = x, ch[x][k ^ 1] = y;
update(y), update(x);
}
void Splay(int p, int x, int want){
while(fa[x] != want){
int y = fa[x], z = fa[y];
if(z != want){
if(which(x) == which(y)) rotate(y);
else rotate(x);
}
rotate(x);
}
if(!want) rt[p] = x;
}
void Ins(int P, int x, ll k){
int cur = rt[P], L = 0;
while(cur && val[cur] != x) L = cur, cur = ch[cur][val[cur] < x];
cur = ++tot, s[cur] = 1, p[cur] = k, fa[cur] = L, val[cur] = x, ch[L][val[L] < x] = cur;
Splay(P, cur, 0);
}
void find(int P, int x){
int cur = rt[P];
while(val[cur] != x) cur = ch[cur][val[cur] < x];
Splay(P, cur, 0);
}
int kth(int P, int k){
int cur = rt[P];
while(true){
if(s[ch[cur][0]] + 1 > k) cur = ch[cur][0];
else if(s[ch[cur][0]] + 1 < k) k -= s[ch[cur][0]] + 1, cur = ch[cur][1];
else{ Splay(P, cur, 0); return cur;}
}
}
int pre(int P, int x){
find(P, x);
int cur = ch[rt[P]][0];
while(ch[cur][1]) cur = ch[cur][1];
Splay(P, cur, 0); return cur;
}
int nxt(int P, int x){
find(P, x);
int cur = ch[rt[P]][1];
while(ch[cur][0]) cur = ch[cur][0];
Splay(P, cur, 0); return cur;
}
void Del(int P, int x){
int Pre = pre(P, x), Nxt = nxt(P, x);
Splay(P, Pre, 0), Splay(P, Nxt, Pre);
ch[Nxt][0] = 0;
}
ll solve(int x, int y){
if(y == m){
ans = kth(la, x + 1), Del(la, val[ans]), Ins(la, ++l[la], p[ans]);
return p[ans];
}
else if(m - 1 - d[x] < y){
ans = kth(x + n, y - (m - 1 - d[x]) + 1), Del(x + n, val[ans]);
tmp = kth(la, x + 1), Del(la, val[tmp]);
Ins(x + n, ++l[x + n], p[tmp]), Ins(la, ++l[la], p[ans]);
return p[ans];
}
else{
int cur = rt[x], L = 0, res = 0, now = 0, F = 0;
while(cur){
now = (!F ? s[ch[cur][0]] : s[ch[cur][0]] + 1); //因为提前插入了 -inf,因此这里每次出列的列数小于它的人个数不一样
if(val[cur] - (res + now) >= y) L = cur, cur = ch[cur][0];
else res += now, F = 1, cur = ch[cur][1];
}
cur = L, ++d[x], Splay(x, cur, 0); // 将 cur 旋转到根方便接下来的讨论
if(s[ch[cur][0]] == 1) ans = y;
else ans = val[cur] - ((val[cur] - s[ch[cur][0]]) - y + 1);
tmp = kth(la, x + 1), Del(la, val[tmp]);
Ins(x, ans, ans + 1ll * (x - 1) * m), Ins(x + n, ++l[x + n], p[tmp]), Ins(la, ++l[la], ans + 1ll * (x - 1) * m);
return ans + 1ll * (x - 1) * m;
}
}
signed main(){
n = read(), m = read(), q = read(), la = 2 * n + 1;
Ins(la, -inf, 0), Ins(la, inf, 0), l[la] = n;
rep(i, 1, n){
l[i] = m, Ins(i, 0, 0), Ins(i, m, 0), Ins(i + n, 0, 0), Ins(i + n, inf, 0); // 将最大值写成 m 有利于接下来 solve 的编写
Ins(la, i, 1ll * i * m); // 注意这里不要把 m 写成 n
}
while(q--) x = read(), y = read(), printf("%lld\n", solve(x, y));
return 0;
}

NOIP2017 Day2T3 列队的更多相关文章

  1. 【NOIP2017】列队(Splay)

    [NOIP2017]列队(Splay) 题面 洛谷 题解 其实好简单啊... 对于每一行维护一棵\(Splay\) 对于最后一列维护一棵\(Splay\) \(Splay\)上一个节点表示一段区间 每 ...

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

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

  3. LUOGU P3960 列队 (noip2017 day2T3)

    传送门 解题思路 记得当时考试我还是个孩子,啥也不会QAQ.现在回头写,用动态开点的线段树,在每行和最后一列开线段树,然后对于每次询问,把x行y列的删去,然后再把x行m列的元素加入x行这个线段树,然后 ...

  4. P2649 - 【NOIP2017】列队

    Description Sylvia 是一个热爱学习的女孩子. 前段时间,Sylvia 参加了学校的军训.众所周知,军训的时候需要站方阵. Sylvia 所在的方阵中有 n×m 名学生,方阵的行数为 ...

  5. 【NOIP2017】 列队

    线段树博客先开个点随笔.... 这意味着啥呢? 今天绝对要把这道题写出来并且更掉这篇blog!!!! ~ upd:懂了哈哈哈哈哈哈哈 先贴代码 回家+讲解 ---------------------- ...

  6. [LUOGU] [NOIP2017] P3960 列队

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

  7. 2018.11.01 loj#2319. 「NOIP2017」列队(线段树)

    传送门 唉突然回忆起去年去noipnoipnoip提高组试水然后省二滚粗的悲惨经历... 往事不堪回首. 所以说考场上真的有debuffdebuffdebuff啊!!!虽然当时我也不会权值线段树 这道 ...

  8. 【NOIP2017】列队 splay

    当年太菜了啊,连$60$分的暴力都没拿满,只打了一个$30$分的. 考虑到这题最多只会询问到$30W$个点,且整个矩阵会去到$30W\times 30W$,显然不能将所有的点存下来. 对于每一行(除最 ...

  9. LOJ2319. 「NOIP2017」列队【线段树】

    LINK 思路 神仙线段树 你考虑怎么样才能快速维护出答案 首先看看一条链怎么做? 首先很显然的思路是维护每个节点的是否出过队 然后对于重新入队的点 直接在后面暴力vector存一下就可以了 最核心的 ...

随机推荐

  1. github项目托管方式(看项目自身是否自带有 .git)

    一.本地仓库和远程仓库建立联系 方式一:项目自身带有 .git文件的[自身就是一个本地仓库的](这里咱以vue-cli3项目为例) 1.创建自带.git本地仓库:创建一个叫 my-vue 的项目 2. ...

  2. 【】(Git)用动图展示10大Git命令

    1.说明 git merge.git rebase.git reset.git revert.git fetch.git pull.git reflog-- 你知道这些 git 命令执行的究竟是什么任 ...

  3. Linux-saltstack-3 saltstack的grains和pillar的基本使用

    @ 目录 一.简介 二.grains 1.查看客户端所有的grains项 2.查看grains的所有的项和值 3.查看某个项和值 (1)语法1: (2)语法2: 4.根据grains来匹配目标主机 例 ...

  4. VirtualBox 虚拟机怎样设置共享文件夹

    首次在VirtualBox装完系统后,很经常用到的操作就是:想将主机的东西拉倒虚拟机进行使用或安装,那怎么将主机的文件拿到虚拟机呢? 1.在虚拟机 > 设置中选择 >安装增强功能,经过这个 ...

  5. 读取 properties 配置文件含有中文的value内容 导致中文乱码 的解决办法

    1.前言 因为装系统的时候把中文写在了系统路径,现在我想把这个路径写在properties里面来读取,可是 发现java 读取会导致中文乱码成 问号????的乱码  ,百度找了好多博客,基本都是一摸一 ...

  6. Python多环境管理神器(pyenv)

    前面我们已经介绍了,python中两种最基础的虚拟环境管理工具,venv和virtualenv,其中virtualenv可以和virtualenvwrapper配合使用.详情请参考:https://w ...

  7. 两张Number()函数图和Boolean()函数图

  8. 记一次 .NET 某药品仓储管理系统 卡死分析

    一:背景 1. 讲故事 这个月初,有位朋友wx上找到我,说他的api过一段时间后,就会出现只有请求,没有响应的情况,截图如下: 从朋友的描述中看样子程序是被什么东西卡住了,这种卡死的问题解决起来相对简 ...

  9. 【笔记】直接使用protocol buffers的底层库,对特定场景的PB编解码进行处理,编码性能提升2.4倍,解码性能提升4.8倍

    接上一篇文章:[笔记]golang中使用protocol buffers的底层库直接解码二进制数据 最近计划优化prometheus的remote write协议,因为业务需要,实现了一个remote ...

  10. Python SQL execute加参数的原理

    在Python中,当用pymysql库,或者MySQLdb库进行数据库查询时,为了防止sql注入,可以在execute的时候,把参数单独带进去,例如: def execute_v1(): config ...