「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;
}
posted @ 2022-09-05 15:26  zuytong  阅读(142)  评论(0编辑  收藏  举报