题解-luogu-p3960列队

题目链接

给定一个$n \times m$的矩阵,每个点编号为$(i - 1) \times m + j$每次抽取一个点,然后让队列先向左再向前,最后将这个点放在$(n,m)$的位置,告知每次离队点的位置,求离队点的编号

$1 \le n,m,q \le 3 \times 10^5$

感谢@oy的贡献

一道非常有意思的题。

假设我们有一个神奇的数据结构,它可以动态地维护一个长度为 $n$ 的队列,其初始元素为 $1,2,…,n$ 。该队列可以支持两种操作,第一种为删除队列的第 $k$ 项元素,执行“向前看齐”操作,并在队列的末尾补充 $n+1$ (以此类推)。第二种为查询队列的第 $k$ 项元素的数值。

拥有这样一个数据结构,本题就简单多了;观察到“向前看齐”命令只对最后一列产生影响,我们可以在每一行维护一个动态队列,最后一列用另一个动态队列单独处理。

如果出列的同学位于最后一列,则只需对最后一列进行一次删除操作即可;如果出列的同学不在最后一列,则需要对出列同学所在的那一行与最后一列同时进行操作。实现细节不再赘述。

现在我们来考虑一下如何实现神奇的动态队列。在本题的情况中,假设每个动态队列最多有 $Q$ 次删除操作,那么动态队列的时空复杂度必须只与 $Q$ 相关,否则 $O(n^2)$ 的时空复杂度无法承受。似乎这里可以用平衡树实现,而我采用了较为好写的动态开点权值线段树来维护动态队列。

初始状态,权值线段树下标为 $1,2,…,n$ 的节点大小均为 $1$ 。对于删除操作,我们只需将对应的权值大小减一,并在权值 $n+1$ 的大小加一即可。对于查询操作,只需在权值线段树中查询第 $k$ 小元素即为对应数值。实现细节同样不再赘述。

记得开 long long 。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
#include <bits/stdc++.h>
#define lson(u) (node[u].l)
#define rson(u) (node[u].r)
#define sum(u) (node[u].sum)
using namespace std;
typedef long long ll;

const int MAXN = 300005;

struct Node {
int l, r, sum;
}node[MAXN * 42];
int cnt;
int root[MAXN], size[MAXN];
vector<ll> ins[MAXN];

int N, M, Q, T;

void modify(int& u, int l, int r, int p, int val) {
if (u == 0) u = ++cnt;
if (l == r) {
sum(u) += val;
return;
}
int mid = (l + r) >> 1;
if (p <= mid) modify(lson(u), l, mid, p, val);
else modify(rson(u), mid + 1, r, p, val);
sum(u) = sum(lson(u)) + sum(rson(u));
}

int query(int u, int l, int r, int k) {
if (l == r) return l;
int mid = (l + r) >> 1;
int lsum = sum(lson(u));
if (l <= T) {
lsum += (mid > T) ? T - l + 1 : mid - l + 1;
}
if (k <= lsum) return query(lson(u), l, mid, k);
else return query(rson(u), mid + 1, r, k - lsum);
}

int main() {
scanf("%d%d%d", &N, &M, &Q);
int Case = Q;
while (Case--) {
int x, y;
scanf("%d%d", &x, &y);
ll ans = 0, tmp = 0;
if (y == M) {
T = N;
ans = query(root[N + 1], 1, N + Q, x);
modify(root[N + 1], 1, N + Q, ans, -1);
modify(root[N + 1], 1, N + Q, N + (++size[N + 1]), 1);
if (ans <= N) ans = ans * (ll)M;
else ans = ins[N + 1][ans - N - 1];
ins[N + 1].push_back(ans);
printf("%lld\n", ans);
} else {
T = M - 1;
ans = query(root[x], 1, M + Q, y);
modify(root[x], 1, M + Q, ans, -1);
modify(root[x], 1, M + Q, M - 1 + (++size[x]), 1);
if (ans <= M - 1) ans += (ll)(x - 1) * M;
else ans = ins[x][ans - M];
T = N;
tmp = query(root[N + 1], 1, N + Q, x);
modify(root[N + 1], 1, N + Q, tmp, -1);
modify(root[N + 1], 1, N + Q, N + (++size[N + 1]), 1);
if (tmp <= N) tmp = tmp * (ll)M;
else tmp = ins[N + 1][tmp - N - 1];
ins[N + 1].push_back(ans);
ins[x].push_back(tmp);
printf("%lld\n", ans);
}
}
return 0;
}

Comments

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×