首先可以观察到这样一个事实,如果 \((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. 【安卓】AndroidStudio使用本地gradle进行build的配置

    1.修改setting使用local gradle2.将下载的gradle-6.7.1-all.zip放入E:/AndroidProject文件夹 修改gradle-wapper.propertie使 ...

  2. request参数获取,参数校验,参数处理

    需求: 1.post接口,需要在过滤器中进行参数校验,校验通过之后再执行方法 2.原有代码中使用x-www-form-urlencoded传参,新需求要使用json格式 3.原有代码校验过滤器使用Se ...

  3. Dapper in .Net Core

    一.前言 关于什么是Dapper,在此不做赘述:本文仅对Dapper在.Net Core中的使用作扼要说明,所陈代码以示例讲解为主,乃抛砖引玉,开发者可根据自身需要进行扩展和调整:其中如有疏漏之处,望 ...

  4. Redisson分布式锁学习总结:可重入锁 RedissonLock#lock 获取锁源码分析

    原文:Redisson分布式锁学习总结:可重入锁 RedissonLock#lock 获取锁源码分析 一.RedissonLock#lock 源码分析 1.根据锁key计算出 slot,一个slot对 ...

  5. 利用自定义动画 animate() 方法,实现某图书网站中“近 7 日畅销榜”中的图书无缝垂直向上滚动特效:当光标移入到图书上时,停止滚动,鼠标移开时,继续滚动

    查看本章节 查看作业目录 需求说明: 利用自定义动画 animate() 方法,实现某图书网站中"近 7 日畅销榜"中的图书无缝垂直向上滚动特效:当光标移入到图书上时,停止滚动,鼠 ...

  6. org.reflections 接口通过反射获取实现类源码研究

    org.reflections 接口通过反射获取实现类源码研究 版本 org.reflections reflections 0.9.12 Reflections通过扫描classpath,索引元数据 ...

  7. Python中类的变量,一个下划线与两个下划线的区别

    形似       功能 __xx 这是私有变量, 只有内部可以访问,外部不可以访问.但是也不是一定不可以访问,只要以 _类名__xx样式就可以访问 .但最好不要这样做,养成良好编程习惯 _x 这是实例 ...

  8. 【IntelliJ IDEA】代码模板

    psvm:main方法 sout:console输出 iter:foreach遍历 fori:for索引遍历

  9. SYCOJ157乘二加一

    题目-乘二加一 (shiyancang.cn) 递归写法 #include <bits/stdc++.h> using namespace std; string f(int n) { i ...

  10. 构造注入链:POP

    1.POP链原理简介: 在反序列化中,我们能控制的数据就是对象中的属性值,所以在PHP反序列化中有一种 漏洞利用方法叫"面向属性编程",即POP( Property Oriente ...