「2021 集训队互测」蜘蛛爬树
考场上绝对不可能想出来的...
就算能想到一点点,但这个复杂度看起来很不正确,很难1继续往下想...
思路
容易想到的是,我们一定是从一棵树的起点 \(s\) 走到某点 \(x\),再从 \(x\) 一直走横路(也就是树与树之间的边)到目标树的 \(x\),再从这棵树的 \(x\) 走到终点 \(t\)
假设 \(w\) 为要走过的横路数量(也就是树之间的距离)
显然我们现在要求的就是 \(dis(s,x)+dis(x,t)+a[x]\times w\)
设 \(z=\text{LCA}(s, t)\),\(y=\text{LCA}(s/t, x)\),\(d_u\) 表示 \((1,u)\) 的距离,进行分类讨论:
假设 \(x\) 在 \(z\) 的子树内:
这时 \(y\) 在 \((s,t)\) 的链上
答案就为 \(d_s+d_t-2d_z+2d_x-2d_y+a[x]\times w\)
显然,\(d_s+d_t-2d_z\) 是一个定值,我们只需要维护 \(k=a[x],~b=2d_x-2d_y\) 的凸包即可
假设 \(x\) 在 \(z\) 子树外:
这时 \(y\) 在 \((1, fa_z)\) 的链上
答案显然为 \(d_s+d_t+2d_x-4d_y+a[x]\times w\)
显然,\(d_s+d_t\) 是一个定值,我们只需要维护 \(k=a[x],~b=2d_x-4d_y\) 的凸包即可
但是,肯定有人问了,这个 \(y\) 会随着 \(z\) 的改变而变化,怎么可能能维护呢?
我们可以考虑用树剖,将每个询问的 \(y\) 剖分成多个重链区间,并离线下来;对于一条重链,重建一次凸包,并处理 \(y\) 位于这个重链上的询问
同时,我们凸包维护的式子也有所变化:对于情况一,\(b=dis(y, x)\);对于情况二,\(b=dis(y, x)-2d_y\)
情况一直接询问最小值;情况二询问完最小值后还要加上 \(2d_z\)
最后所有询问的答案都加上 \(dis(s,t)\)
(还有一些实现上的细节,参考代码及注释)
代码
(如需更好观感,可移步至 LOJ 上查看)
#include<iostream>
#include<fstream>
#include<algorithm>
#include<cmath>
#include<cstdlib>
#include<cstring>
#include<queue>
#include<map>
#include<set>
#include<bitset>
#define LL long long
#define INF (1ll << 60) - 1;
#define FOR(i, x, y) for(int i = (x); i <= (y); i++)
#define ROF(i, x, y) for(int i = (x); i >= (y); i--)
#define PFOR(i, x) for(int i = he[x]; i; i = e[i].nxt)
using std::vector; using std::min;
template<typename T> inline void rd(T &x)
{
T sign = 1, re = 0; char c = getchar();
while(c < '0' || c > '9') {if(c == '-') sign = -1; c = getchar();}
while('0' <= c && c <= '9') {re = re * 10 + (c - '0'); c = getchar();}
x = sign * re;
}
int n, m, q, a[200005];
struct Node
{
int to, nxt; LL w;
}e[400005]; int he[200005];
inline void Edge_add(int u, int v, LL w)
{
static int cnt = 0;
e[++cnt] = (Node){v, he[u], w};
he[u] = cnt;
}
int dep[200005], sz[200005], hs[200005], hr[200005], fa[200005];
int dfn[200005], dcnt; LL dis[200005];
void dfs1(int now, int la)
{
fa[now] = la, dep[now] = dep[la] + 1, sz[now] = 1;
PFOR(i, now)
{
int to = e[i].to;
if(to == la) continue;
dis[to] = dis[now] + e[i].w;
dfs1(to, now);
sz[now] += sz[to];
if(sz[to] > sz[hs[now]]) hs[now] = to;
}
}
void dfs2(int now)
{
dfn[now] = ++dcnt;
if(hs[fa[now]] == now) hr[now] = hr[fa[now]];
else hr[now] = now;
if(hs[now]) dfs2(hs[now]);
PFOR(i, now)
{
int to = e[i].to;
if(to == fa[now] || to == hs[now]) continue;
dfs2(to);
}
}
inline int LCA(int u, int v)
{
while(u != v)
{
if(hr[u] == hr[v]) return dep[u] < dep[v] ? u : v;
dep[hr[u]] > dep[hr[v]] ? u = fa[hr[u]] : v = fa[hr[v]];
}
return u;
}
struct que
{
int s, t, v, id, lca;
inline bool operator < (const que b) const {return v < b.v;}
}qst[200005]; LL ans[200005];
struct line
{
LL k, b;
inline LL y(LL x) {return k * x + b;}
inline bool operator < (const line p) const {return k == p.k ? b > p.b : k > p.k;}
// 维护上凸壳
};
namespace Seg_Tree
{
struct Tree
{
vector<line> a;
int la;
}tr[800005];
inline bool chk(line x, line y, line z) {return (__int128)(y.b - z.b) * (x.k - y.k) >= (__int128)(y.b - x.b) * (z.k - y.k);}
inline void up_convex(vector<line> &a)
{
int sz = a.size(), nsz = 0;
FOR(i, 0, sz - 1)
{
while(nsz > 0 && a[nsz - 1].k == a[i].k) nsz--;
while(nsz > 0 && a[nsz - 1].b >= a[i].b) nsz--;
while(nsz > 1 && chk(a[nsz - 2], a[nsz - 1], a[i])) nsz--; // 判断直线 x, z 组成的凸壳是否将直线 y 覆盖了
a[nsz++] = a[i];
}
a.resize(nsz);
}
#define ls (now << 1)
#define rs ((now << 1) | 1)
inline void up(int now)
{
vector<line> &a = tr[now].a, &la = tr[ls].a, &ra = tr[rs].a;
a.resize(la.size() + ra.size());
std::merge(la.begin(), la.end(), ra.begin(), ra.end(), a.begin());
up_convex(a);
} // 合并两个凸壳
void build(int now, int l, int r, vector<vector<line>> &a)
{
tr[now].la = 0;
if(l == r)
{
std::sort(a[l].begin(), a[l].end());
tr[now].a = a[l];
up_convex(tr[now].a);
return;
}
int mid = (l + r) >> 1;
build(ls, l, mid, a), build(rs, mid + 1, r, a);
up(now);
}
LL query(int now, int l, int r, int L, int R, LL v)
{
if(L <= l && r <= R)
{
auto &[a, la] = tr[now];
while(la + 1 < a.size() && a[la + 1].y(v) < a[la].y(v)) la++;
return a[la].y(v);
}
int mid = (l + r) >> 1; LL re = INF;
if(L <= mid) re = min(re, query(ls, l, mid, L, R, v));
if(mid < R) re = min(re, query(rs, mid + 1, r, L, R, v));
return re;
}
} using namespace Seg_Tree;
struct rm
{
int u, v, i;
};
vector<rm> r1[200005], r2[200005];
inline void Init1(int u, int v, int i)
{
while(hr[u] != hr[v])
{
ans[qst[i].id] = min(ans[qst[i].id], query(1, 1, n, dfn[u], dfn[u] + sz[u] - 1, qst[i].v) - (dis[u] << 1));
// 由于 u 所在的重链有一部分是作为 u 的子树的情况,我们需要在这里计算,下同
r1[hr[u]].emplace_back((rm){hr[u], u, i});
u = fa[hr[u]];
}
ans[qst[i].id] = min(ans[qst[i].id], query(1, 1, n, dfn[u], dfn[u] + sz[u] - 1, qst[i].v) - (dis[u] << 1));
r1[hr[v]].emplace_back((rm){v, u, i});
}
inline void Init2(int u, int i)
{
int lca = u;
u = fa[u];
while(hr[u] != 1)
{
ans[qst[i].id] = min(ans[qst[i].id], query(1, 1, n, dfn[u], dfn[u] + sz[u] - 1, qst[i].v) - (dis[u] << 2) + (dis[lca] << 1));
// 加上 2 倍 dis[lca] 是因为最后加上 (s, t) 的距离时减去了 2 倍 dis[lca],但这种情况并不需要减,下同
r2[hr[u]].emplace_back((rm){hr[u], u, i});
u = fa[hr[u]];
}
ans[qst[i].id] = min(ans[qst[i].id], query(1, 1, n, dfn[u], dfn[u] + sz[u] - 1, qst[i].v) - (dis[u] << 2) + (dis[lca] << 1));
r2[1].emplace_back((rm){1, u, i});
}
void up_dis(int now, LL sum, vector<line> &add)
{
add.emplace_back((line){a[now], sum});
PFOR(i, now)
{
int to = e[i].to;
if(to == fa[now]) continue;
up_dis(to, sum + (e[i].w << 1), add);
}
}
inline void ans1(int rt)
{
vector<vector<line>> rp(1); // 注意这里空间不要直接开 n,否则会导致超时,下同
for(int now = rt; now; now = hs[now])
{
rp.emplace_back(vector<line>(1, (line){a[now], 0}));
PFOR(i, now)
{
int to = e[i].to;
if(to == fa[now] || to == hs[now]) continue;
up_dis(to, e[i].w << 1, rp.back());
}
}
build(1, 1, rp.size() - 1, rp);
for(auto [u, v, i] : r1[rt])
ans[qst[i].id] = min(ans[qst[i].id], query(1, 1, rp.size() - 1, dfn[u] - dfn[rt] + 1, dfn[v] - dfn[rt] + 1, qst[i].v));
}
inline void ans2(int rt)
{
vector<vector<line>> rp(1);
for(int now = rt; now; now = hs[now])
{
rp.emplace_back(vector<line>(1, (line){a[now], -(dis[now] << 1)}));
PFOR(i, now)
{
int to = e[i].to;
if(to == fa[now] || to == hs[now]) continue;
up_dis(to, (e[i].w << 1) - (dis[now] << 1), rp.back());
}
}
build(1, 1, rp.size() - 1, rp);
for(auto [u, v, i] : r2[rt])
ans[qst[i].id] = min(ans[qst[i].id], query(1, 1, rp.size() - 1, dfn[u] - dfn[rt] + 1, dfn[v] - dfn[rt] + 1, qst[i].v) + (dis[qst[i].lca] << 1));
}
signed main()
{
#ifndef ONLINE_JUDGE
freopen("test.in", "r", stdin);
freopen("test.out", "w", stdout);
#endif
rd(n), rd(m), rd(q);
FOR(i, 1, n) rd(a[i]);
FOR(i, 1, n - 1)
{
int u, v; LL w;
rd(u), rd(v), rd(w);
Edge_add(u, v, w), Edge_add(v, u, w);
}
dfs1(1, 0), dfs2(1);
FOR(i, 1, q)
{
LL x, y; rd(x), rd(y);
qst[i] = (que){(x - 1) % n + 1, (y - 1) % n + 1, abs((x - 1) / n - (y - 1) / n), i, 0};
qst[i].lca = LCA(qst[i].s, qst[i].t); ans[i] = INF;
}
std::sort(qst + 1, qst + 1 + q); // 按 v(经过树的棵数)进行排序,使得能够连续地访问凸壳上的直线,降低时间复杂度
vector<vector<line>> rp(n + 1);
FOR(i, 1, n) rp[dfn[i]] = vector<line>(1, (line){a[i], dis[i] << 1});
build(1, 1, n, rp);
FOR(i, 1, q)
{
Init1(qst[i].s, qst[i].lca, i);
Init1(qst[i].t, qst[i].lca, i);
if(fa[qst[i].lca]) Init2(qst[i].lca, i);
// 进行二次离线
}
FOR(i, 1, n) if(hr[i] == i)
ans1(i), ans2(i);
FOR(i, 1, q)
ans[qst[i].id] += dis[qst[i].s] + dis[qst[i].t] - (dis[qst[i].lca] << 1);
FOR(i, 1, q) printf("%lld\n", ans[i]);
return 0;
}