洛谷P3960题解

题意:

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

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

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

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

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

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

我们发现,删除某个数很好维护,直接赋 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;
}
上一篇:range函数


下一篇:python提速技巧