loj6435. 「PKUSC2018」星际穿越
题意
略。
题解
对于做这题的感觉:好难啊,好妙啊,我好菜啊。
首先,需要明确一个结论,就是一个点\(x\)到另一个\(y\)(\(x > y\))有且仅有两种可能最优的走法,要么一直向左走,要么向右走一步后一直向左走。
暴力一点的话我们可以通过\(\mathcal O(n ^ 2)\)的dp进行预处理和\(\mathcal O(n)\)进行单次询问。
其中dp数组要处理出\(f_{i, j}\)代表点\(i\)走\(j\)步,能走到\(i\)左边的最左的点(隐含着\([f_{i, j}, i]\)这段区间的点从\(i\)开始走不超过\(j\)步都能走到)。
如何优化?能否用倍增来完成?好像有点困难。
但是,我们可以令取状态,令\(f_{i, j}\)代表区间\([i, n]\)里的点走\(2 ^ j\)步,能走到\(i\)左边最左的点。
这样,转移即为
但是这样子还是无法优化询问复杂度。
考虑令函数\(C(l, x)\)为\(x\)到\([l, x)\)内的所有点的总步数,则一次询问的和就是\(C(l, x) - C(r + 1, x)\)。
如何计算\(C(l, x)\)?
考虑先从\(x\)处走一步,花费代价为1,可以走到\([l_x, n]\)中的所有点。
这时候再走\(2 ^ i\)步,可以走到区间\([f_{l_x, 2 ^ i}, n]\)中的任意一点。
我们还可以发现一些结论:离\(x\)越远的点需要的步数越多(单调性),因此我们可以通过二分来寻找分段点。
为了这个,我们要预处理\(s_{x, i}\)表示点\(x\)到\([f_{x, i}, x]\)这些点需要的向左走的总步数,则
询问时,按最基本的想法可以对于每个点,做一个二进制拆分,然后把所有贡献加起来。
但是发现对于整段区间\([l, x - 1]\)的点,可以同时进行二进制拆分(因为具有单调性),这里就要用到预处理的\(s\)数组。
复杂度\(\mathcal O((n + q) \log_2 n)\)。
#include <bits/stdc++.h>
#define fi first
#define se second
using namespace std;
typedef long long ll;
const int N = 3e5 + 5, M = 19;
int n, q, arr[N];
int f[M][N]; ll s[M][N];
int lg2 (int x) {
return 32 - __builtin_clz(x);
}
ll solve (int o, int x) {
if (arr[x] <= o) {
return x - o;
}
int co = 1; ll ret = x - arr[x]; x = arr[x];
for (int i = M - 1; ~i; --i) {
if (f[i][x] > o) {
ret += s[i][x] + 1ll * co * (x - f[i][x]);
x = f[i][x], co += 1 << i;
}
}
return ret + 1ll * (co + 1) * (x - o);
}
int main () {
scanf("%d", &n), arr[1] = 1;
for (int i = 2; i <= n; ++i) {
scanf("%d", &arr[i]);
}
f[0][n] = arr[n], s[0][n] = n - f[0][n];
for (int i = n - 1; i; --i) {
f[0][i] = min(f[0][i + 1], arr[i]), s[0][i] = i - f[0][i];
}
for (int j = 1; j < lg2(n); ++j) {
for (int i = n; i; --i) {
if (f[j - 1][i]) {
f[j][i] = f[j - 1][f[j - 1][i]];
s[j][i] = s[j - 1][i] + s[j - 1][f[j - 1][i]] + (1ll << (j - 1)) * (f[j - 1][i] - f[j - 1][f[j - 1][i]]);
}
}
}
scanf("%d", &q);
for (int i = 1, l, r, x; i <= q; ++i) {
scanf("%d%d%d", &l, &r, &x);
ll ouo = solve(l, x) - solve(r + 1, x), ovo = r - l + 1, g = __gcd(ouo, ovo);
printf("%lld/%lld\n", ouo / g, ovo / g);
}
return 0;
}