LOJ6435 「PKUSC2018」星际穿越
题目大意
一张 (n) 个点的无向图,从第 (i) 个点向 ([l_i, i - 1]) 中所有点连无向边,边的长度均为 (1)。记 (mathrm{dist}(i, j)) 表示从点 (i) 到点 (j) 的最短路长度。(q) 次询问,每次给出 (l, r, x)(保证 (l < r < x)),求 (frac{1}{r - l + 1}sum_{y = l}^{r}mathrm{dist}(x, y))。用分数表示。
数据范围:(1leq n, qleq 3 imes 10^5)。
本题题解
先考虑如何快速回答一个 (mathrm{dist}(x, y)) ((x > y))。
如果 (ygeq l_x),答案显然是 (1),特判这种情况。
否则我们至少会走两步。考虑第一步:
- 向左可以走到 ([l_x, x - 1]) 里所有点。
- 向右可以走到所有的 (z),满足 (l_zleq x < z)。
考虑第二步:
- 向左可以走到 (min_{zin[l_x, x -1] ext{ or } l_z leq x < z}{l_z})。注意到这个值一定是 (< x) 的。所以我们把 (x < l_z < z) 的 (z) 算进去也不影响结果。即原式等于 (min_{z = l_x}^{n}{l_z})。换句话说,原问题等价于:第一步可以到达右边任何一个点。且有一个推论:此后再也不需要向右走。
于是我们完成了一个初步转化,从求 (x) 到 (y) 的距离,变成了求 (l_x) 及其右边所有点到 (y) 的距离的最小值。形式化地说:
设 (t(i, j)) 表示从 (i) 或 (i) 右边任意一个点出发,走 (j) 步,能到达的最左边的点(隐含之意是它右边的点都能在 (j) 步以内到达)。则:
- (t(i, 1) = min_{k = i}^{n}{l_k})
- (t(i, j) = min_{k = t(i, j - 1)}^{n}{l_k}quad (jgeq 2))
可以发现一个事实:(t(i, x + y) = t(t(i, x), y)) ((x, ygeq 1))。结合实际意义非常好理解,也就是任何一种走 (x + y) 步的方案,总会经历先走 (x) 步,再走 (y) 步。
于是考虑倍增。设 (f(i, j) = t(i, 2^{j})),也就是从 (i) 或其右边任意一个点出发,走 (2^{j}) 步,能到达的最左边的点。那么:
- (f(i, 0)=min_{k = i}^{n}{l_k})
- (f(i, j) = f(f(i, j - 1), j - 1)quad (jgeq 1))
利用 (f) 数组,可以快速回答一个 (mathrm{dist}(x, y))。首先特判 (ygeq l_x)。然后转化为求 (l_x) 或其右边任意点,到 (y) 的最短路。做法:从 (lfloorlog_2(n) floor) 到 (0)(从大到小)枚举 (2) 的次幂 (j)。如果从当前的 (x) 走 (2^j) 步,到达的点 (f(x, j) > y),则走过去((xgets f(x, j))),并令答案加上 (2^{j})。最终得到的位置 (x) 一定距离 (y) 恰好为 (1)(类似于倍增法求 LCA 时,停在的位置恰好是 LCA 的儿子)。
但这只是求一对 (mathrm{dist}(x, y)),题目要求 (x) 到一段区间的距离和。设 (D(x, y)) 表示 (x) 到 ([y, x - 1]) 里所有点的距离和,则答案等于 (D(x, l) - D(x, r + 1))。可以分别计算。
用一个数组记录从 (i) 或其右边任意点到 ([f(i, j), i - 1]) 里所有点的距离和。即 (s(i, j) = sum_{k = f(i, j)}^{i - 1} min_{u = i}^{n}mathrm{dist}(u, k))。它的转移是:(s(i, j) = s(i, j - 1) + s(f(i, j - 1), j - 1) + (f(i, j - 1)-f(i, j))cdot 2^{j - 1})。
利用 (s) 数组,可以快速回答一个 (D(x, y))。做法:首先特判 (ygeq l_x)。然后分 ([l_x, x - 1]) 和 ([y, l_x)) 里的点两类分别算。前者答案显然是 (x - l_x)。后者可以用上述的倍增法求出。
时间复杂度 (mathcal{O}((n + q)log n))。
参考代码
// problem: LOJ6435
#include <bits/stdc++.h>
using namespace std;
#define mk make_pair
#define fi first
#define se second
#define SZ(x) ((int)(x).size())
typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
template<typename T> inline void ckmax(T& x, T y) { x = (y > x ? y : x); }
template<typename T> inline void ckmin(T& x, T y) { x = (y < x ? y : x); }
const int MAXN = 3e5;
const int LOG = 18;
ll gcd(ll x, ll y) { return (!y) ? x : gcd(y, x % y); }
int n, l[MAXN + 5];
int f[MAXN + 5][LOG + 1];
ll s[MAXN + 5][LOG + 1];
ll calc(int x, int y) {
assert(x >= y);
if (y >= l[x]) {
return x - y;
}
ll res = x - l[x];
x = l[x];
int dis = 1;
for (int j = LOG; j >= 0; --j) {
if (f[x][j] > y) {
res += (ll)dis * (x - f[x][j]) + s[x][j];
dis += (1 << j);
x = f[x][j];
}
}
res += (ll)(dis + 1) * (x - y);
return res;
}
int main() {
cin >> n;
multiset<int> minl;
for (int i = 2; i <= n; ++i) {
cin >> l[i];
minl.insert(l[i]);
}
for (int i = 2; i <= n; ++i) {
f[i][0] = *minl.begin(); // 右边最小的
s[i][0] = (i - f[i][0]);
minl.erase(minl.find(l[i]));
for (int j = 1; j <= LOG; ++j) {
f[i][j] = f[f[i][j - 1]][j - 1];
s[i][j] = s[i][j - 1] + s[f[i][j - 1]][j - 1] + (ll)(f[i][j - 1] - f[i][j]) * (1 << (j - 1));
}
}
int q; cin >> q;
for (int tq = 1; tq <= q; ++tq) {
int l, r, x;
cin >> l >> r >> x;
ll fz = calc(x, l) - calc(x, r + 1);
ll fm = r - l + 1;
ll g = gcd(fz, fm);
fz /= g;
fm /= g;
cout << fz << "/" << fm << endl;
}
return 0;
}