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;
}
浙公网安备 33010602011771号