题意:
给一个 n\times mn×m 的矩阵,矩阵每个位置初始值都是其编号,要求支持如下操作:
对每次给定的点 (x,y)(x,y),输出该位置的值,并将一下矩阵中的以下位置上的数循环左移一位:
(x,y),(x,y+1),(x,y+2),\cdots(x,m),(x+1,m),(x+2,m),\cdots(n,m)(x,y),(x,y+1),(x,y+2),⋯(x,m),(x+1,m),(x+2,m),⋯(n,m)。
做法:
首先从部分分入手。
我们注意到,当 n=1n=1 时,我们需要维护的是:每次对序列循环左移,这个显然可以平衡树。
那么我们此时考虑 nn 和 mm 都较大的情况。
在一个二维图形上循环左移似乎并不可做,但我们可以将操作拆成如下部分:
对于起始点为 (x,y)(x,y) 的操作,我们发现,其等价于以下几个操作的拼接:
- 在矩阵的第 xx 行,删除第 yy 个数,并将该列第 y+1y+1 个到第 m-1m−1 个数全部左移一位;
- 在矩阵第 xx 行的最后放入一个数,值为原矩阵最后一列中的第 xx 个数;
- 在矩阵的最后一列,删除第 xx 个数,并将该列第 x+1x+1 个到第 nn 个数全部左移一位;
- 在矩阵最后一列放入一个数,值为原矩阵中第 xx 行第 yy 个数。
我们惊讶地发现:前两个操作与后两个操作本质上竟然是等价的,只不过是在不同列/行上。
受这个观察的启发,我们也许可以维护 n+1n+1 个序列,每个序列分别代表一个行/列;
那么,对于每一个序列,我们只要维护以下两种操作即可:
- 查询某个位置的值;
- 删除某个位置并将右边所有数左移一位,再在最后的空位上放一个给定的新数。
我们发现,删除某个数很好维护,直接赋 00 即可,难点在于其他数的左移。
这时候就有一个套路了:我们其实不需要真正维护左移操作,
对于一个序列,我们只需要维护一个 0101 序列,
某个位置值为 11 代表原序列该位置有数,值为 00 则代表原序列该位置无数。
此那么左移操作在 0101 序列上就转化为求左数第 kk 个 11,
因为我们每次左移时就不管,查询位置 xx 的值,
就等价于查询 0101 序列左数第 xx 个 11 的位置对应原序列的值。
那么,怎么快速得到序列中每个 11 的位置 pp 对应原序列的值呢?
显然,当 pp 小于原序列的初始长度时,根据题意,这个位置的值就是其在矩阵里的编号,
这个我们可以直接计算;而当 pp 大于原序列初始长度时,这个数一定是后来插入的,
而所有后来插入的数的数量和等于操作数乘 22,故我们可以对每一个序列开一个 vectorvector,
以维护后来插入的数,这样的空间复杂度是有保证的。
而维护上面的 0101 序列,我们只需要动态开点线段树即可,因为序列初始全为 11。
至于细节可以看代码,代码部分参考了洛谷上某篇题解。
code:
#define fi first
#define se second
#define mp make_pair
#define pb push_back
#define LL long long
#define pii pair < int , int >
#define ckmax(a, b) ((a) = max((a), (b)))
#define ckmin(a, b) ((a) = min((a), (b)))
#define rep(i, a, b) for (int i = (a); i <= (b); i++)
#define per(i, a, b) for (int i = (a); i >= (b); i--)
#define edg(i, v, u) for (int i = head[u], v = e[i].to; i; i = e[i].nxt, v = e[i].to)
using namespace std;
inline int read() {
int x = 0, f = 1; char ch = getchar();
while (ch < '0' || ch > '9') f = ch == '-' ? -1 : 1, ch = getchar();
while (ch >= '0' && ch <= '9') x = x * 10 + ch - 48, ch = getchar();
return x * f;
}
const int N (6e5 + 10);
const int M (2e7 + 10);
int tot;
int q, c;
int n, m;
int rt[N];
vector < LL > a[N];
struct Tree {
int sum;
int ch[2];
} t[M];
#define ls(p) t[p].ch[0]
#define rs(p) t[p].ch[1]
#define mid ((l + r) >> 1)
#define lsn ls(p), l, mid
#define rsn rs(p), mid + 1, r
void ins (int &p, int l, int r, int x) {
if (!p) p = ++tot; t[p].sum++; if (l == r) return;
if (x <= mid) ins (lsn, x); else ins (rsn, x);
}
int kth (int p, int l, int r, int k) {
if (l == r) return l;
int sl = mid - l + 1 - t[ls(p)].sum;
if (k <= sl) return kth (lsn, k);
return kth (rsn, k - sl);
}
LL shu (int x, LL val) {
int k = kth (rt[n + 1], 1, c, x);
ins (rt[n + 1], 1, c, k);
LL res = (k <= n) ? (1ll * m * k) : a[n + 1][k - n - 1];
return a[n + 1].pb (val ? val : res), res;
}
LL heng (int x, int y) {
int k = kth (rt[x], 1, c, y);
ins (rt[x], 1, c, k);
LL res = (k < m) ? (1ll * (x - 1ll) * m + 1ll * k) : a[x][k - m];
LL ttt = shu (x, res);
return a[x].pb (ttt), res;
}
int main() {
n = read(), m = read(), q = read();
c = max (n, m) + q;
rep (i, 1, q) {
int x = read(), y = read();
LL ans = (y ^ m) ? heng (x, y) : shu (x, 0);
printf ("%lld\n", ans);
}
return 0;
}