2022 International Collegiate Programming Contest, Jinan Site 部分題目簡要題解

從這裏開始

Problem B Torch

  注意到 $a_1, b_1, a_2, b_2$ 的和不會超過 $10^6$

  考慮胖先生的週期開始的時候,瘦先生的週期在時刻 $t$,距離胖先生的距離爲 $x + 1$,那麼胖先生的週期結束的時候,瘦先生的距離胖先生的距離大概是 $\max(x + c, 0) + 1$ 之類的東西,其中 $c$ 是一個和 $t$ 有關的常數。

  這個信息不難合併。對於每個詢問,相當於是若干個循環再加上一個前綴和之類的東西,然後再處理一下一個週期中零散的情況。

  時間複雜度 $O(a_1 + b_1 + n)$。

Code

#include <bits/stdc++.h>
using namespace std;

#define ll long long

class Input {
  public:

#define digit(_x) ((_x) >= '0' && (_x) <= '9')

    template <typename T>
    Input& operator >> (T &u) {
      char x;
      while (~(x = getchar()) && !digit(x));
      for (u = x - '0'; ~(x = getchar()) && digit(x); u = u * 10 + x - '0');
      return *this;
    }
} in;

class Output {
  public:
    
    Output& operator << (ll u) {
      static char stk[30];
      char *p = stk + 26;
      *p = 0;
      if (!u)
        *(--p) = '0';
      while (u) {
        *(--p) = u % 10 + '0';
        u /= 10;
      }
      puts(p);
      return *this;
    }
} out;

typedef class Data {
  public:
    ll x, y;

    Data(ll x = 0, ll y = 0) : x(x), y(y) { }

    Data operator + (Data b) {
      return Data(x + b.x, max(y + b.x, b.y));
    }

    Data pow(ll p) {
      return p ? Data(x * p, max(y + x * (p - 1), y)) : Data(0, 0);
    }

    ll eval() {
      return max(x, y);
    }
} Data;

int T;
int n, m;
int a1, b1, a2, b2, q;
vector<Data> f;

ll calc_step(ll x) {
  return (x / m) * a2 + min(x % m, (ll) a2);
}
ll calc_step(ll l, ll r) {
  return calc_step(r) - calc_step(l - 1);
}
ll calc_step1(ll x) {
  return (x / n) * a1 + min(x % n, (ll) a1);
}

Data prepare(int s) {
  ll d = a1 - calc_step(s + 1, s + n);
  return Data(d, 0);
}

void solve() {
  in >> a1 >> b1 >> a2 >> b2 >> q;
  n = a1 + b1;
  m = a2 + b2;
  f = {Data(0, 0)};
  int r = 0;
  do {
    f.push_back(prepare(r));
    r = (r + n) % m;
  } while (r);
  for (int i = 1; i < (signed) f.size(); i++) {
    f[i] = f[i - 1] + f[i];
  }
  ll qt, qtc, C = 1ll * n * (f.size() - 1);
  while (q--) {
    in >> qt;
    qtc = qt / C;
    int cr = (qt / n) % (f.size() - 1);
    r = qt % n;
    Data g = f.back().pow(qtc) + f[cr] + Data(min(r, a1) - calc_step(qt - r + 1, qt), 0);
    ll ans = calc_step1(qt) - g.eval();
    out << ans;
  }
}

int main() {
  in >> T;
  while (T--) {
    solve();
  }
  return 0;
}

Problem C DFS Order 2

  考慮一個點在 dfs 序在 $x$ 的方案數相當於它到根節點的祖先,每個把自己節點做揹包。同時每個祖先處做揹包還要維護一下在這個點之前選了多少棵子樹。

  直接通過合併前後的揹包複雜度會炸,改成二元多項式除就可以了。

Code

  考場上寫過了,感覺還有點小胖,懶得再寫一遍了。

Problem F Grid Points

  你看我這老年人像是會補 0 隊 ac 的題目的嗎

Problem G Quick Sort

  模擬一下這個過程可以發現 $p$ 的值要麼是 $pivot$ 要麼是 $pivot - 1$

  考慮枚舉兩邊中少的一邊。找交換的時候另外一邊的數的時候,考慮維護一下每個值的位置,然後把少的半邊的值拉出來排序。

Code

#include <bits/stdc++.h>
using namespace std;

class Reader {
  public:

#define digit(_x) ((_x) >= '0' && (_x) <= '9')

    Reader& operator >> (int &u) {
      char x;
      while (~(x = getchar()) && !digit(x));
      for (u = x - '0'; ~(x = getchar()) && digit(x); u = u * 10 + x - '0');
      return *this;
    }

} in;

const int N = 5e5 + 5;

int T, n;
int ans;
int p[N], q[N];

void do_swap(int x, int y) {
  swap(p[x], p[y]);
  swap(q[p[x]], q[p[y]]);
  ans++;
}

void solve(int l, int r) {
  static vector<int> tmp;
  if (l >= r)
    return;
  int mid = (l + r) >> 1;
  int pvt = p[mid], pos = mid, mir = r;
  tmp.clear();
  if (pvt <= mid) {
    for (int i = l; i <= pvt; i++) {
      tmp.push_back(q[i]);
    }
    sort(tmp.begin(), tmp.end(), greater<int>());
    for (int i = l, j = 0; i <= pvt; i++) {
      if (p[i] >= pvt && i < tmp[j]) {
        mir = tmp[j];
        do_swap(i, tmp[j++]);
      }
    }
  } else {
    for (int i = r; i >= pvt; i--) {
      tmp.push_back(q[i]);
    }
    sort(tmp.begin(), tmp.end());
    for (int i = r, j = 0; i >= pvt; i--) {
      if (p[i] <= pvt && i > tmp[j]) {
        mir = i;
        do_swap(i, tmp[j++]);
      }
    }
  }
  pos = pvt - (p[pvt] > pvt || pvt == mir);
//  cerr << l << " " << r << " " << pos << endl;
  solve(l, pos);
  solve(pos + 1, r);
}

void solve() {
  in >> n;
  for (int i = 1; i <= n; i++) {
    in >> p[i];
    q[p[i]] = i;
  }
  ans = 0;
  solve(1, n);
  printf("%d\n", ans);
}

int main() {
  in >> T;
  while (T--) {
    solve();
  }
  return 0;
}

Problem H Set of Intervals

  首先特判 $n = 1$。 

  考慮判定一區間 $[x, y] (x < y)$ 是否可行。可以注意到如果有一個區間包含 $x$,那麼在之後的合併過程中,我可以讓它包含 $x$

  如果一個區間包含 $x$,一個包含 $y$ ,那麼顯然我通過一次合併可以得到 $[x, y]$

  現在考慮怎樣湊出一個包含 $x$ 的區間:

  • 本身存在一個區間包含 $x$
  • $x$ 兩側分別至少存在 1 個區間

  顯然除了這兩種情況,剩下都是不可能的

  考慮掃描線,計算 $x = l$ 的時候可能的右端點的範圍。

  暴力的做法是枚舉 $x$ 是怎麼湊出來的,刪掉這些區間,然後求剩下的區間中最小的左端點 $L$ 以及最大的右端點 $R$,可能的右端點的範圍爲 $[L, R]$

  每次最多隻會刪掉 2 個區間,所以如果 $n$ 足夠大,這些可能的右端點的範圍的並集就是所有的 $L$ 取 min,右端點取 max(當 2 種湊 $x$ 方案使得剩下的區間只要有 1 個公共的時候,可以保證這兩個 $[L, R]$ 不是相離的)。當 $n$ 足夠大的時候,要麼就是刪掉 $1$ 個或者 $2$ 個區間求 $[L, R]$,要麼就是對所有區間的求。

  如果 $n$ 比較小,直接暴力就行了。

Code

#include <bits/stdc++.h>
using namespace std;

const int N = 1e5 + 5;

typedef class Segment {
  public:
    int l, r;

    Segment(int l = 0, int r = 0) : l(l), r(r) {  }

    bool operator < (Segment b) const {
      return (l ^ b.l) ? (l < b.l) : r < b.r;
    }
} Segment;

typedef class Event {
  public:
    int p;
    int l, r;

    Event(int p, int l, int r) : p(p), l(l), r(r) { }

    bool operator < (Event b) const {
      return p < b.p;
    }
} Event;

#define ll long long

int T, n;
multiset<int> Sl, Sr;
multiset<Segment> Ssl, Ssm, Ssr;

ll eval(ll l1, ll l2, ll r1, ll r2) {
  ll ret = 0;
  r1 = max(l1, r1);
  l2 = min(l2, r2);
  if (l2 < l1 || r2 < r1)
    return 0;
  // sum_{i = l1}^{l2}  r2 - max(r1, i + 1) + 1
  vector<ll> br {l1, l2 + 1, r1};
  sort(br.begin(), br.end());
  for (int i = 0; i < 2; i++) {
    ll l = br[i], r = br[i + 1] - 1;
    l = max(l, l1), r = min(r, l2);
    if (l > r) {
      continue;
    }
    ll vl = r2 - max(r1, l + 1) + 1;
    ll vr = r2 - max(r1, r + 1) + 1;
    ret += (vl + vr) * (r - l + 1) / 2;
  }
//  cerr << l1 << " " << l2 << " " << r1 << " " << r2 << " " << ret << endl;
  return ret;
}

void erase(Segment s) {
  Sl.erase(Sl.find(s.l));
  Sr.erase(Sr.find(s.r));
}
void insert(Segment s) {
  Sl.insert(s.l);
  Sr.insert(s.r);
}

Segment eval(Segment s) {
  erase(s);
  Segment ret (*Sl.begin(), *--Sr.end());
  insert(s);
  return ret;
}
Segment eval(Segment s1, Segment s2) {
  erase(s1), erase(s2);
  Segment ret (*Sl.begin(), *--Sr.end());
  insert(s1), insert(s2);
  return ret;
}

void solve() {
  scanf("%d", &n);
  Sl.clear(), Sr.clear();
  Ssl.clear(), Ssm.clear(), Ssr.clear();
  vector<Event> E;
  for (int i = 1, l, r; i <= n; i++) {
    scanf("%d%d", &l, &r);
    Sl.insert(l);
    Sr.insert(r);
    Ssr.insert(Segment(l, r));
    E.emplace_back(l, l, r);
    E.emplace_back(r + 1, l, r);
  }
  if (n == 1) {
    puts("1");
    return;
  }
  sort(E.begin(), E.end());
  ll ans = 0;
  int L, R, l0 = *Sl.begin(), r0 = *--Sr.end();
  auto it = E.begin(), _it = E.end(), pit = E.begin();
  while (it != _it) {
    pit = it;
    L = pit->p;
    while (it != _it && it->p == pit->p) {
      Segment s (it->l, it->r);
      if (it->p == it->l) {
        Ssr.erase(Ssr.find(s));
        Ssm.insert(s);
      } else {
        Ssm.erase(Ssm.find(s));
        Ssl.insert(s);
      }
      it++;
    }
    if (it == _it)
      break;
    R = it->p;
    int szl = Ssl.size(), szm = Ssm.size(), szr = Ssr.size();
    if (n < 8) {
      vector<Segment> vs;
      for (auto sm : Ssm) {
        vs.push_back(eval(sm));
      }
      if (n > 2) {
        for (auto sl : Ssl) {
          for (auto sr : Ssr) {
            vs.push_back(eval(sl, sr));
          }
        }
      }
      sort(vs.begin(), vs.end());
      int l = -1, r = -1;
      for (auto s : vs) {
        if (s.l > r) {
          if (r != -1)
            ans += eval(L, R - 1, l, r);
          l = s.l, r = s.r;
        } else {
          r = max(r, s.r);
        }
      }
      if (r != -1) {
        ans += eval(L, R - 1, l, r);
      }
    } else if (szm + min(szl, szr) >= 2) {
      ans += eval(L, R - 1, l0, r0);
    } else if (szm == 1) {
      Segment s = eval(*Ssm.begin());
      ans += eval(L, R - 1, s.l, s.r);
    } else if (szl == 1) {
      Segment s = eval(*Ssl.begin());
      ans += eval(L, R - 1, s.l, s.r);
    } else if (szr == 1) {
      Segment s = eval(*Ssr.begin());
      ans += eval(L, R - 1, s.l, s.r);
    } 
  }
  printf("%lld\n", ans);
}

int main() {
  scanf("%d", &T);
  while (T--) {
    solve();
  }  
  return 0;
}

Problem I Shortest Path

  傻逼學院負責人

  假設有一條路徑繞一個很大的環(環大小至少爲 $3$),設上面的最小的邊爲 $e$,那麼我可以通過較短的一條路徑走到 $e$,然後在 $e$ 上左右橫跳若干次,再折返回來,如果奇偶性不對那麼這個環是個奇環,再把這個環繞一圈就可以了。

  剩下就很傻逼了。給路徑長度設一個閾值 $L$,然後求一下經過每條邊,路徑長度爲 $L, L + 1$ 的最短路徑。路徑長度大於 $L$ 的時候搞個凸包就可以了。

Code

#include <bits/stdc++.h>
using namespace std;

#define ll long long

const int Mod = 998244353;

template <typename T>
void pfill(T* pst, T* ped, T val) {
  for ( ; pst != ped; *(pst++) = val);
}

typedef class Zi {
  public:
    int v;

    Zi() : v(0) { }
    Zi(int x) : v(x) {  }
    Zi(ll x) : v(x % Mod) { } 
    
    friend Zi operator + (Zi a, Zi b) {
      int x = a.v + b.v;
      return (x >= Mod) ? (x - Mod) : x;
    }
    friend Zi operator - (Zi a, Zi b) {
      int x = a.v - b.v;
      return (x < 0) ? (x + Mod) : x;
    }
    friend Zi operator * (Zi a, Zi b) {
      return 1ll * a.v * b.v;
    }
    Zi& operator += (Zi b) {
      return *this = *this + b;
    }
} Zi;

template <typename T>
bool vmin(T& a, T b) {
  return a > b ? (a = b, true) : false;
}

const int N = 2020, M = 5020;

typedef class Edge {
  public:
    int v, w;
    
    Edge(int v, int w) : v(v), w(w) { }
} Edge;

const ll llf = 2e18;

int n, m, X;
ll fs[N << 1][N], ft[N << 1][N];
vector<Edge> G[N];

void do_dp(int s, int T, ll f[][N]) {
  for (int i = 0; i <= T; i++) {
    for (int j = 1; j <= n; j++) {
      f[i][j] = llf;
    }
  }
  f[0][s] = 0;
  for (int t = 1; t <= T; t++) {
    for (int p = 1; p <= n; p++) {
      for (auto e : G[p]) {
        vmin(f[t][p], f[t - 1][e.v] + e.w);
      }
    }
  }
}

ll ceil(ll a, ll b) {
  return (a + b - 1) / b;
} 

ll better(pair<int, ll> a, pair<int, ll> b) {
  int k1 = a.first, k2 = b.first;
  ll b1 = a.second, b2 = b.second;
  // k1 > k2
  // k1 * t + b1 >= k2 * t + b2
  // t >= (b2 - b1) / (k1 - k2)
  return ceil(b2 - b1, k1 - k2);
}

Zi sum2(ll n) {
  return (n * (n + 1)) >> 1;
}
Zi sum2(ll l, ll r) {
  return sum2(r) - sum2(l - 1);
}

pair<int, ll> stk[M << 2];
Zi solve(bool par, int T) {
  static int ws[M << 1];
  static ll vs[M << 1], tmpw[M << 1];
  int S = T - par;
  vector<pair<int, ll>> E;
  int m2 = m << 1 | 1;
  pfill(vs, vs + m2, llf);
  m2 = 0;
  for (int p = 1; p <= n; p++) {
    for (auto e : G[p]) {
      ws[m2++] = e.w;
    }
  }
  for (int t = 0; t < S; t++) {
    int c = 0;
    for (int p = 1; p <= n; p++) {
      for (auto e : G[p]) {
        tmpw[c] = fs[t][p];
        c++;
      }
    }
    c = 0;
    for (int p = 1; p <= n; p++) {
      for (auto e : G[p]) {
        vmin(vs[c], tmpw[c] + ft[S - t - 1][e.v] + e.w);
        c++;
      }
    }
  }
  for (int i = 0; i < m2; i++) {
    if (vs[i] ^ llf) {
      E.emplace_back(ws[i] << 1, vs[i]);
    }
  }
  sort(E.begin(), E.end(), greater<pair<int, ll>>());
  int tp = 0;
  for (auto e : E) {
    while (tp && stk[tp].first == e.first)
      vmin(e.second, stk[tp--].second);
    while (tp >= 2 && better(stk[tp - 1], stk[tp]) >= better(stk[tp], e))
      tp--;
    stk[++tp] = e;
  }
  Zi ret = 0;
  // S + 2 * t <= X
  ll lim = (X - S) >> 1, lst = 1;
  for (int i = 1; i <= tp && lst <= lim; i++) {
    int k = stk[i].first;
    ll b = stk[i].second;
    ll R = min(lim + 1, i == tp ? lim + 1 : better(stk[i], stk[i + 1]));
    if (R <= lst) {
      continue;
    } 
    // \sum_{j = lst}^{R - 1} (b + k * j)
    ret += Zi(b) * (R - lst) + sum2(lst, R - 1) * k;
    lst = R;
  }
  return ret;
}

void solve() {
  scanf("%d%d%d", &n, &m, &X);
  for (int i = 1; i <= n; i++) {
    G[i].clear();
  }
  for (int i = 1, u, v, w; i <= m; i++) {
    scanf("%d%d%d", &u, &v, &w);
    G[u].emplace_back(v, w);
    if (u ^ v) {
      G[v].emplace_back(u, w);
    }
  }
  int T = min((n << 1) + 10, X);
  do_dp(1, T, fs);
  do_dp(n, T, ft);
  Zi ans = 0;
  for (int i = 1; i <= T; i++) {
    if (fs[i][n] < llf) {
      ans += fs[i][n];
    }
  }
  if (X > T) {
    ans += solve(0, T);
    ans += solve(1, T);
  }
  printf("%d\n", ans.v);
}

int main() {
  int T;
  scanf("%d", &T);
  while (T--) {
    solve();
  }
  return 0;
}

Problem J Skills

   考慮 3 維 dp 滾動後實際轉移是怎麼樣的:

  • 每行每列求個最值
  • 每行每個做一個等差數列加

  顯然這能斜率優化,維護 $O(n)$ 個凸包就可以了

  寫完感覺自己代碼長度不太對,瞄了一下題解。怪誒,爲什麼我印象裏值域是 $10^9$

  Emmmm.......值域很小,所以一個技能不會連續不學太多天。

Code

#include <bits/stdc++.h>
using namespace std;

#define for_st(_x, _y) for (int _x = 0; (_x) < 3; (_x)++) for (int _y = 0; (_y) < 3; (_y)++) if ((_x) ^ (_y))

const signed int inf = 1e9 + 9;

const int N = 1005;

template <typename T>
void pfil(T* pst, T* ped, T val) {
  for ( ; pst != ped; *(pst++) = val);
}

template <typename T>
bool vmax(T& a, T b) {
  return (a < b) ? (a = b, true) : false;
}

typedef class dp_t {
  public:
    int tg, tp, fr;
    vector<int> f;
    vector<int> g;
    vector<int> stk;

    void init(int n) {
      tg = 0, fr = tp = 0;
      f.resize(n + 1);
      g.resize(n + 1);
      stk.resize(n + 1);
      for (int i = 0; i <= n; i++) {
        f[i] = g[i] = -inf;
        stk[i] = 0;
      }
    }

    void add(int v) {
      tg += v;
    }
    
    // x < y
    int better(int x, int y) {
      return (g[x] - g[y]) / (y - x);
    }

    void append(int t, int v) {
      f[t] = v - tg;
      g[t] = f[t] - ((t * (t + 1)) >> 1);
      while (tp > fr + 1 && better(stk[tp - 1], stk[tp]) >= better(stk[tp], t))
        tp--;
      while (tp > fr && g[stk[tp]] <= g[t])
        tp--;
      stk[++tp] = t; 
    }

    int calc(int x, int t) {
      return f[x] + tg - (((t - x) * (t - x - 1)) >> 1);
    } 

    int query(int t) {
      if (fr == tp)
        return -inf;
      int tmp = calc(stk[fr + 1], t), foo;
      while (fr + 1 < tp && (foo = calc(stk[fr + 2], t)) >= tmp) {
        tmp = foo;
        fr++;
      }
      return tmp;
    }

    void log() {
      cerr << fr << " " << tp << " " << tg << " " << endl;
      for (auto x : f)
        cerr << x << ' ';
      cerr << endl;
      for (auto x : g)
        cerr << x << ' ';
      cerr << endl;
      for (int i = 1; i <= tp; i++)
        cerr << stk[i] << ' ';
      cerr << endl;
    }
} dp_t;

int T, n;
int a[N][3];
int f[N][3];
int tmpv[3][3][N];
dp_t g[3][3];
// first is the outside index
dp_t h[3][3][N];
// dp_t h01[N], h02[N], h12[N];
// dp_t h10[N], h20[N], h21[N];

void solve() {
  scanf("%d", &n);
  for (int i = 1; i <= n; i++) {
    for (int j = 0; j < 3; j++) {
      scanf("%d", a[i] + j);
      f[i][j] = f[i - 1][j] + a[i][j];
    }
  }
  int ans = max(f[n][0], max(f[n][1], f[n][2]));
  for_st(x, y)
    g[x][y].init(n);
  for_st(x, y) {
    for (int i = 1; i <= n; i++) {
      h[x][y][i].init(n);
    }
  }
  for (int i = 2; i <= n; i++) {
    for_st(x, y) {
      for (int j = 1; j < i - 1; j++) {
        tmpv[x][y][j] = g[x][y].calc(j, i);
      }
    }
    for_st(x, y) {
      int z = 3 - x - y;
      for (int j = 1; j < i - 1; j++) {
        vmax(tmpv[z][y][j], h[x][y][j].query(i));
      }
    }
    for_st(x, y) {
      int z = 3 - x - y;
      for (int j = 1; j < i - 1; j++) {
        h[x][y][j].append(i - 1, tmpv[x][y][j]);
        h[x][y][j].add(a[i][z] - (i - j));
        h[y][x][i - 1].append(j, tmpv[x][y][j] + (((i - j) * (i - j - 1)) >> 1));
      }
      h[y][x][i - 1].add(a[i][z] - 1);
    }
    for_st(x, y)  
      tmpv[x][y][0] = max(f[i - 1][y], g[y][x].query(i));
    for_st(x, y) {
      g[x][y].append(i - 1, tmpv[x][y][0]);
      g[x][y].add(a[i][x]);
    }
  }
//  h[0][1][3].log();
  for_st(x, y) {
    vmax(ans, g[x][y].query(n + 1));
    for (int i = 1, tmp; i < n; i++) {
      vmax(ans, tmp = h[x][y][i].query(n + 1));
//      cerr << x << " " << y << " " << i << " " << tmp << endl;
    }
  }
  printf("%d\n", ans);
}

int main() {
  scanf("%d", &T);
  while (T--) {
    solve();
  }
  return 0;
}

Problem L Tree Distance

  考慮 $dist(u, v) = dep(u) + dep(v) - 2 \times dep(lca(u, v))$

  考慮點分治,考慮硬點分治中心是路徑的 lca,因爲 lca 深度那一項是減,所以如果不是真正的 lca 的話存在更小的答案。

  那麼對於一個分治區域內的點,肯定是選擇深度最小的和次小的作爲答案,考慮一對點 $(u, v)$ 不妨設 $u < v$,如果能貢獻到答案,那麼要滿足 $[u, v]$ 中 $u, v$ 的深度是最小值或者次小值,討論 $u$ 是區間最小還是 $v$ 是區間最小,然後搞個單調棧就可以了。

  現在只有 $O(n\log n)$ 個候選點對。直接掃描線處理詢問就可以了。

  upd:這裏掃描線因爲只有添加點,查後綴,所以可以用樹狀數組(

Code

#include <bits/stdc++.h>
using namespace std;

#define ll long long

class Input {
  public:

#define digit(_x) ((_x) >= '0' && (_x) <= '9')

    template <typename T>
    Input& operator >> (T &u) {
      char x;
      while (~(x = getchar()) && !digit(x));
      for (u = x - '0'; ~(x = getchar()) && digit(x); u = u * 10 + x - '0');
      return *this;
    }
} in;

class Output {
  public:
    
    Output& operator << (ll u) {
      static char stk[30];
      char *p = stk + 26;
      *p = 0;
      if (u < 0) {
        putchar('-');
        u = -u;
      }
      if (!u)
        *(--p) = '0';
      while (u) {
        *(--p) = u % 10 + '0';
        u /= 10;
      }
      puts(p);
      return *this;
    }
} out;

template <typename T>
bool vmin(T& a, T b) {
  return a > b ? (a = b, true) : false;
}

const ll llf = 2e18;

const int N = 2e5 + 5, M = 1e6 + 5;

typedef class Event {
  public:
    int op, l, r;
    ll v;

    Event(int op, int l, int r, ll v) : op(op), l(l), r(r), v(v) {  }

    bool operator < (Event b) const {
      return r == b.r ? op < b.op : r < b.r;
    }
} Event;

typedef class Edge {
  public:
    int ed, w;

    Edge(int ed, int w) : ed(ed), w(w) {  }
} Edge;

typedef class Node {
  public:
    ll v;
    Node *l, *r;

    void upd(ll x) {
      vmin(v, x);
    }
    void push_up() {
      v = min(l->v, r->v);
    }
} Node;

Node pool[N << 1];
Node* tp = pool;

typedef class SegmentTree {
  public:
    int n;
    Node* rt;

    SegmentTree(int n) : n(n) {
      build(rt, 1, n);
    }
    void build(Node*& p, int l, int r) {
      p = tp++;
      p->v = llf;
      if (l ^ r) {
        int mid = (l + r) >> 1;
        build(p->l, l, mid);
        build(p->r, mid + 1, r);
      }
    } 

    void modify(Node* p, int l, int r, int x, ll v) {
      if (l == r) {
        p->upd(v);
        return;
      }
      int mid = (l + r) >> 1;
      if (x <= mid) {
        modify(p->l, l, mid, x, v);
      } else {
        modify(p->r, mid + 1, r, x, v);
      }
      p->push_up();
    }
    void modify(int p, ll v) {
      modify(rt, 1, n, p, v);
    }

    ll query(Node* p, int l, int r, int ql, int qr) {
      if (l == ql && r == qr) {
        return p->v;
      }
      int mid = (l + r) >> 1;
      if (qr <= mid) {
        return query(p->l, l, mid, ql, qr);
      } else if (ql > mid) {
        return query(p->r, mid + 1, r, ql, qr);
      }
      ll vl = query(p->l, l, mid, ql, mid);
      ll vr = query(p->r, mid + 1, r, mid + 1, qr);
      return min(vl, vr);
    }
    ll query(int l, int r) {
      return query(rt, 1, n, l, r);
    }
} SegmentTree;

int n, q;
ll ans[M];
vector<Edge> G[N];
vector<Event> E;

int sz[N];
bool ban[N];

int get_sz(int p, int fa) {
  sz[p] = 1;
  for (auto E : G[p]) {
    int e = E.ed;
    sz[p] += ((e == fa || ban[e]) ? 0 : get_sz(e, p));
  }
  return sz[p];
}

int get_G(int p, int fa, int half_size) {
  for (auto E : G[p]) {
    int e = E.ed;
    if ((e ^ fa) && !ban[e] && sz[e] > half_size) {
      return get_G(e, p, half_size);
    }
  }
  return p;
}

ll dep[N];
vector<int> V;
void get_dep(int p, int fa, ll d) {
  V.push_back(p);
  dep[p] = d;
  for (auto E : G[p]) {
    int e = E.ed;
    if ((e ^ fa) && !ban[e]) {
      get_dep(e, p, d + E.w);
    }
  }
}

int stk[N];
void dividing(int x) {
  int g = get_G(x, 0, get_sz(x, 0) >> 1);
  ban[g] = true;
  V.clear();
  get_dep(g, 0, 0);
  int tp = 0, q;
  sort(V.begin(), V.end());
  for (auto p : V) {
    while (tp && dep[q = stk[tp]] >= dep[p]) {
      E.emplace_back(0, q, p, dep[q] + dep[p]);
      tp--;
    }
    stk[++tp] = p;
  }
  reverse(V.begin(), V.end());
  tp = 0;
  for (auto p : V) {
    while (tp && dep[q = stk[tp]] > dep[p]) {
      E.emplace_back(0, p, q, dep[p] + dep[q]);
      tp--;
    }
    stk[++tp] = p;
  }
  for (auto E : G[g]) {
    int e = E.ed;
    if (!ban[e]) {
      dividing(e);
    }
  }
}

int main() {
  in >> n;
  for (int i = 1, u, v, w; i < n; i++) {
    in >> u >> v >> w;
    G[u].emplace_back(v, w);
    G[v].emplace_back(u, w);
  }
  in >> q;
  for (int i = 1, l, r; i <= q; i++) {
    in >> l >> r;
    if (l == r) {
      ans[i] = -1;
    } else {
      E.emplace_back(1, l, r, i);
    }
  }
  dividing(1);
  sort(E.begin(), E.end());
  SegmentTree st (n);
  for (auto e : E) {
    if (!e.op) {
      st.modify(e.l, e.v);
//      cerr << "M " << e.l << " " << e.v << endl;
    } else {
      ans[e.v] = st.query(e.l, e.r);
//      cerr << "Q " << e.l << " " << e.r << endl;
    }
  }
  for (int i = 1; i <= q; i++) {
    out << ans[i];
  }
  return 0;
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章