首先可以观察到这样一个事实,如果 \((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. SOA 和微服务

    ====>场景 不可能让客户端与6个不同的应用/系统都一一去通信来去完成数据的展示.而是6个应用/系统之间进行彼此通信来完成调用,最后客户端只需要调用一个接口来获取数据即可. SOA架构 SOA ...

  2. 【stm32】基于hal库使用野火指南者esp8266 WIFI模块进行TCP传输

    UART.c #include "stm32f1xx_it.h" #include "LED.h" #include "UART.h" #i ...

  3. SMOOTHING (LOWPASS) SPATIAL FILTERS

    目录 FILTERS Box Filter Kernels Lowpass Gaussian Filter Kernels Order-Statistic (Nonlinear) Filters Go ...

  4. TensorFlow.NET机器学习入门【8】采用GPU进行学习

    随着网络越来约复杂,训练难度越来越大,有条件的可以采用GPU进行学习.本文介绍如何在GPU环境下使用TensorFlow.NET. TensorFlow.NET使用GPU非常的简单,代码不用做任何修改 ...

  5. [Guide]Google Python Style Guide

    扉页 项目主页 Google Style Guide Google 开源项目风格指南 - 中文版 背景 Python 是Google主要的脚本语言.这本风格指南主要包含的是针对python的编程准则. ...

  6. IntelliJ IDEA 2019.3 代码提示忽略大小写(IDEA 2019版本如何设置代码提示不分大小写?)

    最近在使用IDEA,发现每次只能进行完全匹配,且区分大小写,界面变了IDEA 2019.3 忽略大小写设置跟之前的版本稍微有点不同,跟之前的软件有点点区别,在此记录一下不区分大小写的方法. 1. 使用 ...

  7. 编写Java程序,将一个int型数组拼接成字符串

    返回本章节 返回作业目录 需求说明: 将一个int数组中的元素拼接成int元素以逗号分隔字符串. 实现思路: 定义一个数组变量int[] arrs = {12,21,33,9,2}. 定义一个方法ar ...

  8. yum 下载安装包以及依赖包

    有时候我在用yum安装软件,依赖包比较多,还受网速的影响.所以我们可以将安装包以及依赖包下载到本地安装,这样会快捷很多. yum 提供了这种功能 yum -y install yum-utils 下载 ...

  9. epoll实现原理

    作者:蓝形参链接:https://www.zhihu.com/question/20122137/answer/14049112来源:知乎 首先我们来定义流的概念,一个流可以是文件,socket,pi ...

  10. Linux shell 脚本中使用 alias 定义的别名

    https://www.cnblogs.com/chenjo/p/11145021.html 核心知识点: 用 shopt 开启和关闭 alias 扩展 交互模式下alias 扩展默认是开启的,脚本模 ...