P3960 题解
P3960 题解
题意:
给一个 \(n\times m\) 的矩阵,矩阵每个位置初始值都是其编号,要求支持如下操作:
对每次给定的点 \((x,y)\),输出该位置的值,并将一下矩阵中的以下位置上的数循环左移一位:
\((x,y),(x,y+1),(x,y+2),\cdots(x,m),(x+1,m),(x+2,m),\cdots(n,m)\)。
做法:
首先从部分分入手。
我们注意到,当 \(n=1\) 时,我们需要维护的是:每次对序列循环左移,这个显然可以平衡树。
那么我们此时考虑 \(n\) 和 \(m\) 都较大的情况。
在一个二维图形上循环左移似乎并不可做,但我们可以将操作拆成如下部分:
对于起始点为 \((x,y)\) 的操作,我们发现,其等价于以下几个操作的拼接:
- 在矩阵的第 \(x\) 行,删除第 \(y\) 个数,并将该列第 \(y+1\) 个到第 \(m-1\) 个数全部左移一位;
- 在矩阵第 \(x\) 行的最后放入一个数,值为原矩阵最后一列中的第 \(x\) 个数;
- 在矩阵的最后一列,删除第 \(x\) 个数,并将该列第 \(x+1\) 个到第 \(n\) 个数全部左移一位;
- 在矩阵最后一列放入一个数,值为原矩阵中第 \(x\) 行第 \(y\) 个数。
我们惊讶地发现:前两个操作与后两个操作本质上竟然是等价的,只不过是在不同列/行上。
受这个观察的启发,我们也许可以维护 \(n+1\) 个序列,每个序列分别代表一个行/列;
那么,对于每一个序列,我们只要维护以下两种操作即可:
- 查询某个位置的值;
- 删除某个位置并将右边所有数左移一位,再在最后的空位上放一个给定的新数。
我们发现,删除某个数很好维护,直接赋 \(0\) 即可,难点在于其他数的左移。
这时候就有一个套路了:我们其实不需要真正维护左移操作,
对于一个序列,我们只需要维护一个 \(01\) 序列,
某个位置值为 \(1\) 代表原序列该位置有数,值为 \(0\) 则代表原序列该位置无数。
此那么左移操作在 \(01\) 序列上就转化为求左数第 \(k\) 个 \(1\),
因为我们每次左移时就不管,查询位置 \(x\) 的值,
就等价于查询 \(01\) 序列左数第 \(x\) 个 \(1\) 的位置对应原序列的值。
那么,怎么快速得到序列中每个 \(1\) 的位置 \(p\) 对应原序列的值呢?
显然,当 \(p\) 小于原序列的初始长度时,根据题意,这个位置的值就是其在矩阵里的编号,
这个我们可以直接计算;而当 \(p\) 大于原序列初始长度时,这个数一定是后来插入的,
而所有后来插入的数的数量和等于操作数乘 \(2\),故我们可以对每一个序列开一个 \(vector\),
以维护后来插入的数,这样的空间复杂度是有保证的。
而维护上面的 \(01\) 序列,我们只需要动态开点线段树即可,因为序列初始全为 \(1\)。
至于细节可以看代码,代码部分参考了洛谷上某篇题解。
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;
}