Solution - Holes
Link。
暴力做是 \(O(nm)\) 的。怎么优化呢?I've no slightest idea😢
结果用到了一个特别神的东西,罗阿姨认为 useless 的东西——分块。想到这个就豁然开朗了!
假设块长为 \(\sqrt{n}\),\(f_i\) 表示从 \(i\) 开始跳在块内的步数,\(to_i\) 表示从 \(i\) 开始跳在块内的最后到达的点。我们分成若干段,如果对于 \(i\),跳完之后还在同一块内就简单转移一下,否则说明跳到其它块里去了,相当于当前块里的结束点,\(to_i\) 即本身,那么 \(f_i = 1, to_i = i\)。
询问的话,每次先算当前块内的,然后跳到 \(to_i\) 跳的下一个点,这样就会计算下一个块。如果 \(to_i\) 再跳一步就 \(> n\) 了,直接跳出循环,否则说明还会计算下一个块,直接跳就可以了。\(O(\sqrt{n})\)。
更新只更新一段。\(O(\sqrt{n})\)。
总的就是 \(O(m \sqrt{n})\)。由于神秘原因块长设为 \(2 \sqrt{n}\) 才可以过。
原因是询问比较多,这样平衡一下。
namespace liuzimingc {
const int N = 2e5 + 5; // i + pos[i] 可能会 RE,开两倍,也可以单独判一下是否 > n
#define endl '\n'
int n, m, p[N], f[N], to[N], block, pos[N], l[N], r[N];
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr); cout.tie(nullptr);
cin >> n >> m;
block = (int)2 * sqrt(n);
for (int i = 1; i <= n; i++) {
cin >> p[i];
pos[i] = (i - 1) / block + 1;
}
for (int i = 1; i <= n; i++)
if (!l[pos[i]]) l[pos[i]] = i;
for (int i = n; i; i--)
if (!r[pos[i]]) r[pos[i]] = i;
for (int i = n; i; i--)
if (pos[i + p[i]] == pos[i]) f[i] = f[i + p[i]] + 1, to[i] = to[i + p[i]]; // 简单转移一下
else f[i] = 1, to[i] = i;
while (m--) {
int op, a;
cin >> op >> a;
if (op) {
int sum = 0;
while (true) {
sum += f[a];
a = to[a];
if (a + p[a] > n) break;
a += p[a];
}
cout << a << " " << sum << endl;
}
else {
cin >> p[a];
for (int i = r[pos[a]]; i >= l[pos[a]]; i--)
if (pos[i + p[i]] == pos[i]) f[i] = f[i + p[i]] + 1, to[i] = to[i + p[i]];
else f[i] = 1, to[i] = i;
}
}
return 0;
}
} // namespace liuzimingc
Posted by liuzimingc