PKUSC2018 星際穿越
阿新 • • 發佈:2018-11-28
問題
有一個數軸,每個點 \(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; }