1. 程式人生 > >PKUSC2018 星際穿越

PKUSC2018 星際穿越

https://loj.ac/problem/6435

問題

有一個數軸,每個點 \(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;
}