[CERC2017] Donut Drone 题解
给一种不用倍增的不带 \(log\) 的做法。
约定 \(\mathrm{up}_i,\mathrm{down}_i,\mathrm{left}_i,\mathrm{right}_i\) 表示 \(x/y\) 坐标在 \(i\) 位置时向上 / 下 / 左 / 右方向循环移动一步的位置。
首先考虑暴力,即从起点直接模拟走 \(k\) 步到达目标点,时间复杂度 \(\mathcal O\left(\sum k\right)\),而 \(k\) 是 \(10^9\) 量级,显然不行。
简单观察可以发现,在图不改变的情况下从图中任意一点出发的情形是相同的,而图大小只有 \(r\times c\),这说明走至多 \(r\times c\) 步一定会走到一个已经经过的点,则会形成一个循环节,而循环节长度也至多是 \(r\times c\) 的。
那么问题转化成快速走完 \(r\times c\) 步,考虑在不带修改的情况下怎么做,发现时间复杂度可接受的上限是 \(\mathcal O\left(m(r+c)\right)\),这启示我们可以运用分块的思想,将 \(r\times c\) 步分成若干个 \(c\) 步和若干个 \(1\) 步,即分解成 \(pc+q\) 的形式,然后每次跳 \(c\) 步,一个粗劣的想法是预处理一个 \(\mathrm{skip}_{i,j}\) 表示从位置 \((i,j)\) 走 \(c\) 步能走到哪里,这样我们可以直接用倍增或别的什么方法预处理 \(\mathrm{skip}\) 数组和循环节,先不论时间复杂度,特别是带上修改以后,实现就非常困难。
考虑为什么实现这么困难,究其根本是需要维护的东西太多了,我们希望能通过询问时的一些常数开销减少维护量,然后我们发现我们可以只维护第一列的信息,这就相对好维护多了,先预处理出 \(\mathrm{step}_{i,j}\) 表示 \((i,j)\) 走 \(1\) 步能走到 \((\mathrm{step}_{i,j},\mathrm{right}_j)\) ,\(\mathrm{skip}_i\) 表示 \((i,1)\) 跳 \(c\) 步后会回到 \((\mathrm{skip}_i,1)\),循环节可以在询问时找,具体每次询问时先走到第一列,在第一列跳直到找到循环节或者遇到了剩余步数不到 \(c\),然后对循环节取模,然后再跳 \(c\) 步最多跳 \(r\) 次,再走最多 \(c\) 步,那么我们就将单次询问的时间复杂度压在了 \(\mathcal O\left(r+c\right)\)。
我们发现我们的询问时间复杂度已经非常优美了,实现难度也不大,我们可以接下来考虑让修改去迎合询问的操作,分别考虑一次修改对 \(\mathrm{step}\) 和 \(\mathrm{skip}\) 的影响,先考虑较为简单的 \(\mathrm{step}\),答案发现修改 \((x,y)\) 只会对 \((\mathrm{up}_x,\mathrm{left}_y),(x,\mathrm{left}_y),(\mathrm{down}_x,\mathrm{left}_y)\),三个位置产生影响,将这三个位置重新做就好了,那么再考虑如果 \(\mathrm{step}_{x,y}\) 的修改会对 \(\mathrm{skip}\) 产生的影响,即将会走到 \((x,y)\) 的每一行的 \(\mathrm{skip}\) 都修改为 \((x,y)\) 走到的那个 \((p,1)\) 的 \(p\)。
想要求出 \(p\) 是很简单的,直接走到 \(p\) 的位置就好了,但是想要处理会被影响的 \(\mathrm{skip}\) 还是有些难度的,但我们可以发现一个性质,即被影响的 \(\mathrm{skip}\) 一定构成一个区间,证明也非常简单,我们发现如果有一组 \([l,r]\) 满足 \(\mathrm{skip}_l=p,\mathrm{skip}_r=p\),但存在一个 \(t\) 满足 \(t\in(l,r),\mathrm{skip}\ne p\),则 \(t\) 想走向另一个终点所需要经过的路径一定和 \(l\) 或 \(r\) 走向 \(p\) 的路径有交或交叉,这两种情况都是不可能的,证明可以自行画图手模一下。
这样的话就可以很方便地求出答案了,从 \(x\) 慢慢反向推到 \(1\),做到 \(k\) 时,维护一组 \([l,r]\) 表示从 \(\forall i\in[l,r],\ (i,k)\to(p,1)\),我们可以一步一步往前推,具体实现可以看代码,时间复杂度是 \(\mathcal O\left(c\right)\) 的。
这样的话加上预处理的时间复杂度总时间复杂度就是 \(\mathcal O\left(rc+m(r+c)\right)\),比倍增法少一个 \(log\)。
c++ 代码
#include<bits/stdc++.h>
using namespace std;
#define Reimu inline void // 灵梦赛高
#define Marisa inline int // 魔理沙赛高
#define Sanae inline bool // 早苗赛高
typedef long long LL;
typedef unsigned long long ULL;
typedef pair<int, int> Pii;
typedef tuple<int, int, int> Tiii;
#define fi first
#define se second
template<typename Ty>
Reimu clear(Ty &x) { Ty().swap(x); }
const int N = 2010;
int n, m, px, py;
int l[N], r[N], u[N], d[N], skip[N], vis[N];
int a[N][N], step[N][N];
Reimu update(int i, int j) {
int t = r[j], t1 = u[i], t2 = d[i];
if (a[i][t] > a[t1][t] && a[i][t] > a[t2][t]) step[i][j] = i;
else step[i][j] = a[t1][t] > a[t2][t] ? t1 : t2;
}
Marisa calc(int x, int y) {
while (y ^ 1) x = step[x][y], y = r[y];
return x;
}
Reimu move(int c) {
memset(vis + 1, 0, n << 2);
while (c && py ^ 1) px = step[px][py], py = r[py], --c;
int cur = 1;
while (c >= m && !vis[px]) vis[px] = cur++, px = skip[px], c -= m;
if (vis[px]) c %= m * (cur - vis[px]);
while (c >= m) px = skip[px], c -= m;
while (c--) px = step[px][py], py = r[py];
cout << px << ' ' << py << '\n';
}
Reimu solve(int x, int y) {
int p = calc(step[x][y], r[y]), L = x, R = x, len = 1;
for (int i = y; --i && len > 0 && len < n; ) {
if (step[u[L]][i] == L) L = u[L], ++len;
else if (step[L][i] ^ L && step[L][i] ^ d[L]) L = d[L], --len;
if (step[d[R]][i] == R) R = d[R], ++len;
else if (step[R][i] ^ R && step[R][i] ^ u[R]) R = u[R], --len;
}
if (len <= 0) return;
if (len >= n) L = d[R];
if (L <= R) for (int i = L; i <= R; ++i) skip[i] = p;
else {
for (int i = 1; i <= R; ++i) skip[i] = p;
for (int i = L; i <= n; ++i) skip[i] = p;
}
}
int main() {
ios::sync_with_stdio(false); cin.tie(nullptr);
cin >> n >> m;
iota(u + 1, u + n + 1, 0); iota(d + 1, d + n + 1, 2);
iota(l + 1, l + m + 1, 0); iota(r + 1, r + m + 1, 2);
d[u[1] = n] = r[l[1] = m] = 1;
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
cin >> a[i][j];
}
}
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
update(i, j);
}
}
for (int i = 1; i <= n; ++i) skip[i] = calc(step[i][1], 2);
px = py = 1;
int T; cin >> T; while (T--) {
string s; int x; cin >> s >> x;
if (s[0] ^ 'c') move(x);
else {
int y, v; cin >> y >> v;
a[x][y] = v;
for (int t: {u[x], x, d[x]}) update(t, l[y]);
for (int t: {u[x], x, d[x]}) solve(t, l[y]);
}
}
return 0;
}