首先可以观察到这样一个事实,如果 ((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
", solve(x, y));
return 0;
}