问题
有一个数轴,每个点 \(i\) 向 \([L_i, i-1]\) 中的所有点连双向边。每次询问 \((l,r,x)\),求 \(\sum_{i=l}^{r} dist(x,i)\),其中 \(l < r < x\)。
题解
容易发现,最优的路径在开始时向右走最多一次,然后一直向左走。
我们先不管向右一步的代价,对于每个点 \(i\) 求出 \(M_{i}\) 表示 \([i,n]\) 中最小的 \(L_i\),然后用一棵可持久化线段树扫一遍。
接下来处理向右走的,可以分为两种情况:
(1) 先从 \(x\) 走到 \(L_x\),然后向左走。
(2) 向左走到一个 \(L_y\) 最左的点 \(y\),然后走到 \(L_y\)。
看上去不是很好直接处理,但是注意到 \(x \rightarrow L_x\) 和 \(x \rightarrow y\) 都是走一步,因此可以先跳一步到 \(L_x\),然后再对 \(L_x\) 这个位置的线段树查询。
#include <bits/stdc++.h>
using namespace std;
typedef long long i64;
const int N = 300010;
const int M = 65 * N;
struct node_t {
int l, r, add;
i64 sum;
} tree[M];
int n, m, total = 1, root[N], z[N], mn[N];
void update(int &x, int y, int l, int r, int ll, int rr, int v) {
tree[x = total++] = tree[y];
if (ll <= l && r <= rr) {
tree[x].add += v;
return;
}
tree[x].sum += (min(rr, r)) - (max(ll, l)) + 1;
int mid = (l + r) >> 1;
if (ll <= mid) {
update(tree[x].l, tree[y].l, l, mid, ll, rr, v);
}
if (mid < rr) {
update(tree[x].r, tree[y].r, mid + 1, r, ll, rr, v);
}
}
i64 query(int x, int l, int r, int ll, int rr, int acc) {
acc += tree[x].add;
if (ll <= l && r <= rr) {
return tree[x].sum + acc * (r - l + 1);
}
int mid = (l + r) >> 1;
i64 res = 0;
if (ll <= mid) {
res = query(tree[x].l, l, mid, ll, rr, acc);
}
if (mid < rr) {
res += query(tree[x].r, mid + 1, r, ll, rr, acc);
}
return res;
}
int main() {
scanf("%d", &n);
for (int i = 1; i < n; i++) {
scanf("%d", &z[i]);
z[i]--;
}
mn[n - 1] = z[n - 1];
for (int i = n - 2; i >= 1; i--) {
mn[i] = min(mn[i + 1], z[i]);
}
for (int i = 1; i < n; i++) {
int pr = mn[i];
update(root[i], root[pr], 0, n - 1, 0, i - 1, 1);
}
scanf("%d", &m);
for (int i = 0; i < m; i++) {
int l, r, x;
scanf("%d %d %d", &l, &r, &x);
l--; r--; x--;
int p = z[x];
i64 ans = r - l + 1, len = r - l + 1;
if (l < p) {
ans += query(root[p], 0, n - 1, l, min(r, p - 1), 0);
}
i64 d = __gcd(ans, len);
printf("%lld/%lld\n", ans / d, len / d);
}
return 0;
}
这个过程其实也可以用倍增实现。
#include <bits/stdc++.h>
using namespace std;
typedef long long i64;
const int N = 300010;
int n, m, a[N], go[20][N];
i64 dist[20][N];
i64 query(int l, int x) {
if (a[x] <= l) {
return x - l;
}
i64 res = x - a[x];
i64 acc = 1;
x = a[x];
for (int j = 19; j >= 0; j--) {
if (go[j][x] > l) {
res += dist[j][x] + acc * (x - go[j][x]);
acc += 1 << j;
x = go[j][x];
}
}
res += (x - l) * (acc + 1);
return res;
}
int main() {
scanf("%d", &n);
a[0] = -1;
for (int i = 1; i < n; i++) {
scanf("%d", &a[i]);
a[i]--;
}
go[0][n - 1] = a[n - 1];
for (int i = n - 2; i >= 0; i--) {
go[0][i] = min(go[0][i + 1], a[i]);
dist[0][i] = i - go[0][i];
}
for (int t = 0; t < 19; t++) {
long long p2 = 1 << t;
for (int i = 0; i < n; i++) {
if (go[t][i] == -1) {
go[t + 1][i] = -1;
} else {
go[t + 1][i] = go[t][go[t][i]];
dist[t + 1][i] = dist[t][i] + dist[t][go[t][i]] + p2 * (go[t][i] - go[t + 1][i]);
}
}
}
scanf("%d", &m);
for (int i = 0; i < m; i++) {
int l, r, x;
scanf("%d %d %d", &l, &r, &x);
l--; r--; x--;
i64 p = query(l, x) - query(r + 1, x);
i64 q = r - l + 1;
i64 d = __gcd(p, q);
printf("%lld/%lld\n", p / d, q / d);
}
return 0;
}