题意
略。
题解
对于做这题的感觉:好难啊,好妙啊,我好菜啊。
首先,需要明确一个结论,就是一个点(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
", ouo / g, ovo / g);
}
return 0;
}