题意
有一个 $n$ 行 $m$ 列的方阵,初始时第 $i$ 行第 $j$ 列的学生的编号是 $(i-1) imes m+j$。每当有一个同学离队后(不能有两人同时离队),方阵中所有学生会先向左看齐,再向前看齐,然后离队学生归队后补在第 $n$ 行第 $m$ 列的位置上。给定每次离队学生的位置,求该学生的编号。
分析
观察可知每次学生离队只会对该行,最后一行和最后一列产生影响
我们可以建 $n+1$ 棵支持单点修改的线段树,前 $n$ 棵包含第 $n$ 行的前 $m-1$ 个学生,第 $n+1$ 棵包含最后一列
所以对于位置为 $(x,y)$ 的学生离开,只需要修改第 $x$ 棵和第 $n+1$ 棵线段树,也就是先输出并删去 $x$ 树的第 $y$ 个元素,将其加入 $n+1$ 树的最后一个位置,再删去 $n+1$ 树的第 $n$ 个元素,将其加入 $x$ 树的最后一个位置
因此每棵线段树的最大长度为 $max(n,m)+q$($q$ 为询问次数)
由于本题数据较大,开 $n+1$ 棵线段树肯定会超内存,所以需要动态开点
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> #include <vector> #include <cmath> using namespace std; #define ll long long #define inf 0x7fffffff #define N 300005 #define M 10000005 int n, m, q, a, num, now; int id[N], len[N], sz[M], ls[M], rs[M]; ll val[M]; inline int get_sz(int l,int r) { if (now == n + 1) { if (r <= n) return r - l + 1; if (l <= n) return n - l + 1; return 0; } if (r < m) return r - l + 1; if (l < m) return m - l; return 0; } ll query(int &p, int l, int r, int x) { if (!p) { p = ++num; sz[p] = get_sz(l, r); if (l == r) { if (now <= n) val[p] = (ll)(now - 1) * m + l; else val[p] = (ll)l * m; } } sz[p]--; if (l == r) return val[p]; int mid = (l + r) >> 1; if ((!ls[p] && x <= (mid - l + 1)) || x <= sz[ls[p]]) return query(ls[p], l, mid, x); else { if (!ls[p]) x -= (mid - l + 1); else x -= sz[ls[p]]; return query(rs[p], mid + 1, r, x); } } void update(int &p, int l, int r, int x, ll w) { if(!p) { p = ++num; sz[p] = get_sz(l,r); if (l == r) val[p] = w; } sz[p]++; if (l == r) return; int mid = (l + r) >> 1; if (x <= mid) update(ls[p], l, mid, x, w); else update(rs[p], mid + 1, r, x, w); } int main() { scanf("%d%d%d", &n, &m, &q); a = max(n, m) + q; for (int i = 1; i <= q; i++) { int x, y; ll z; scanf("%d%d", &x, &y); if (y == m) now = n + 1, z = query(id[now], 1, a, x); else now = x, z = query(id[now], 1, a, y); printf("%lld ", z); now = n + 1; update(id[now], 1, a, n + (++len[now]), z); if (y != m) { z = query(id[now], 1, a, x); now = x; update(id[now], 1, a, m - 1 + (++len[now]), z); } } return 0; }