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)\) 的操作,我们发现,其等价于以下几个操作的拼接:

  1. 在矩阵的第 \(x\) 行,删除第 \(y\) 个数,并将该列第 \(y+1\) 个到第 \(m-1\) 个数全部左移一位;
  2. 在矩阵第 \(x\) 行的最后放入一个数,值为原矩阵最后一列中的第 \(x\) 个数;
  3. 在矩阵的最后一列,删除第 \(x\) 个数,并将该列第 \(x+1\) 个到第 \(n\) 个数全部左移一位;
  4. 在矩阵最后一列放入一个数,值为原矩阵中第 \(x\) 行第 \(y\) 个数。

我们惊讶地发现:前两个操作与后两个操作本质上竟然是等价的,只不过是在不同列/行上。

受这个观察的启发,我们也许可以维护 \(n+1\) 个序列,每个序列分别代表一个行/列;

那么,对于每一个序列,我们只要维护以下两种操作即可:

  1. 查询某个位置的值;
  2. 删除某个位置并将右边所有数左移一位,再在最后的空位上放一个给定的新数。

我们发现,删除某个数很好维护,直接赋 \(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;
}
posted @ 2021-12-23 21:25  GaryH  阅读(34)  评论(0编辑  收藏  举报