点分治
2025-06-12

一时兴起在博客里搜索『点分治』,发现没有匹配项。


定义

适用于解决和点对、路径相关的问题。

对于任意点 \(x\),树上任意简单路径 \((u,v)\) 被分为几类:

  1. 不经过 \(x\)
  2. 经过 \(x\)
    • 一个端点为 \(x\)
    • 两个端点都不为 \(x\):可以由上一种情况拼接得到。

如果我们把每个点作为 \(x\) 的情况都枚举一遍,再统计不重复的 \((u,v)\),在一种特定的枚举顺序下可以做到 \(O(n\log n)\) 完成所有点对的枚举。

证明略,每次取子树重心,只遍历没遍历过的点即可。

关于子树重心,一则阅读材料:一种基于错误的寻找重心方法的点分治的复杂度分析


CF1575E Eye-Pleasing City Park Tour

https://codeforces.com/problemset/problem/1575/E

随便用线段树维护一下就行了。复杂度 \(O(n\log^2 n)\)

随便维护一下

#include <bits/stdc++.h>
const int mod = 1e9 + 7;
int main() {
#ifdef ONLINE_JUDGE
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr), std::cout.tie(nullptr);
#else
    std::freopen(".in", "r", stdin);
    std::freopen(".out", "w", stdout);
#endif
    int n, k;
    std::cin >> n >> k;
    std::vector<int> a(n + 1);
    for (int i = 1; i <= n; ++i)
        std::cin >> a[i];
    std::vector<std::vector<std::pair<int, int> > > g(n + 1);
    for (int i = 1, x, y, c; i < n; ++i) {
        std::cin >> x >> y >> c;
        g[x].emplace_back(y, c), g[y].emplace_back(x, c);
    }
    std::vector<std::vector<std::pair<int, long long> > > bit(2, std::vector<std::pair<int, long long> > (n + 1));
    auto lowbit = [&](int x) {
        return x & -x;
    };
    auto add = [&](int id, int x, long long v, int type) {
        for (++x; x <= n; x += lowbit(x))
            bit[id][x].first += type, (bit[id][x].second += v * type) %= mod;
        return;
    };
    auto ask = [&](int id, int x) {
        std::pair<int, long long> res;
        for (++x; x; x -= lowbit(x))
            res.first += bit[id][x].first, (res.second += bit[id][x].second) %= mod;
        return res;
    };
    std::vector<int> p, siz(n + 1), mx(n + 1), tag(n + 1);
    std::function<void(int, int)> findp = [&](int x, int fa) {
        p.push_back(x);
        siz[x] = 1, mx[x] = 0;
        for (auto [i, c] : g[x])
            if (i != fa && !tag[i]) {
                findp(i, x);
                siz[x] += siz[i];
                mx[x] = std::max(mx[x], siz[i]);
            }
        return;
    };
    auto findrt = [&](int x) {
        std::vector<int>().swap(p), findp(x, -1);
        int n = (int)p.size();
        for (auto i : p)
            if (mx[i] <= n / 2 && n - siz[i] <= n / 2)
                return i;
        return -1;
    };
    auto res(0ll);
    std::function<void(int, int, int, int, int, long long, int, int)> calc = [&](int x, int fa, int oc, int la, int cnt, long long s, int os, int type) {
        if (cnt > k)
            return;
        if (type == 0) {
            auto t0(ask(oc, k - cnt)), t1(ask(!oc, k - cnt - 1));
            // auto lr(res);
            (res += t0.first * (s - os) + t0.second) %= mod;
            (res += t1.first * (s - os) + t1.second) %= mod;
            // printf("  %d(%d, %lld): ask[0](%d) = (%d, %lld), ask[1](%d) = (%d, %lld), res += %lld\n", x, cnt, s - os, k - cnt, t0.first, t0.second, k - cnt - 1, t1.first, t1.second, res - lr);
        }
        else if (type != 0) {
            // printf("  %d(%d, %lld): add(%d, %lld)\n", x, cnt, s - os, oc, s * type);
            add(oc, cnt, s, type);
        }
        for (auto [i, c] : g[x])
            if (i != fa && !tag[i])
                calc(i, x, oc, c, cnt + (la != c), (s + a[i]) % mod, os, type);
        return;
    };
    std::function<void(int)> DFS = [&](int x) {
        x = findrt(x);
        // printf("DFS %d\n", x);
        for (auto [i, c] : g[x])
            if (!tag[i]) {
                calc(i, x, c, c, 0, (a[x] + a[i]) % mod, a[x], 0);
                calc(i, x, c, c, 0, (a[x] + a[i]) % mod, a[x], 1);
            }
        (res += ask(0, k).second + ask(1, k).second) %= mod;
        (res += a[x]) %= mod;
        // printf("res += %lld + %lld\n", ask(0, k).second, ask(1, k).second);
        for (auto [i, c] : g[x])
            if (!tag[i])
                calc(i, x, c, c, 0, (a[x] + a[i]) % mod, a[x], -1);
        // assert(!ask(0, k).second);
        tag[x] = 1;
        for (auto [i, c] : g[x])
            if (!tag[i])
                DFS(i);
        return;
    };
    DFS(1);
    std::cout << (res + mod) % mod << '\n';
    return 0;
}

A - 三元图 Graf

https://www.luogu.com.cn/problem/P10829

发现很圆方树;观察题图,肯定想要把中间的方点作为树根。

发现它就是重心;进一步地,整个圆方树其实是一个点分治的结构。显然除了最后一层,每次找到的重心都应该是方点;并且其应该有三个等大的儿子。check 上述两点即可。

唉还是挺常规的,限制我做出来这道题的应该是我已经 > 1y 没写过连通性问题了 😅

怎么还有人不会判重边的 😅

#include <bits/stdc++.h>
int main() {
#ifdef ONLINE_JUDGE
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr), std::cout.tie(nullptr);
#else
    std::freopen(".in", "r", stdin);
    std::freopen(".out", "w", stdout);
#endif
    int n, m;
    std::cin >> n >> m;
    std::vector<std::vector<int> > g(n + 1), _g(n + 1);
    auto nosol = [](void) {
        std::cout << "ne" << '\n';
        exit(0);
        return;
    };
    std::set<std::pair<int, int> > t;
    for (int x, y; m--; ) {
        std::cin >> x >> y;
        if (x == y || t.count({ x, y }))
            nosol();
        t.insert({ x, y }), t.insert({ y, x });
        _g[x].push_back(y), _g[y].push_back(x);
    }
    std::vector<int> st, dfn(n + 1), low(n + 1);
    std::function<void(int, int)> DFS = [&](int x, int fa) {
        static int now = 0;
        st.push_back(x);
        dfn[x] = low[x] = ++now;
        for (auto i : _g[x])
            if (!dfn[i]) {
                DFS(i, x);
                low[x] = std::min(low[x], low[i]);
                if (low[i] >= dfn[x]) {
                    ++n, g.emplace_back();
                    for (int p = st.back(); ; p = st.back()) {
                        g[n].push_back(p), g[p].push_back(n);
                        st.pop_back();
                        if (p == i)
                            break;
                    }
                    g[n].push_back(x), g[x].push_back(n);
                }
            }
            else if (i != fa)
                low[x] = std::min(low[x], dfn[i]);
        return;
    };
    int n1 = n;
    DFS(1, -1);
    std::vector<int> siz(n + 1), p, mx(n + 1), tag(n + 1);
    DFS = [&](int x, int fa) {
        siz[x] = 1, mx[x] = 0;
        p.push_back(x);
        for (auto i : g[x])
            if (i != fa && !tag[i]) {
                DFS(i, x);
                siz[x] += siz[i];
                mx[x] = std::max(mx[x], siz[i]);
            }
        return;
    };
    auto findrt = [&](int x) {
        p.clear(), DFS(x, -1);
        int n = (int)p.size();
        for (auto i : p)
            if (mx[i] <= n / 2 && n - siz[i] <= n / 2)
                return i;
        return -1;
    };
    std::function<void(int)> DFS1 = [&](int x) {
        x = findrt(x), DFS(x, -1);
        int si = -1, cnt = 0;
        if (siz[x] == 1) {
            if (x > n1)
                nosol();
            return;
        }
        if (x <= n1)
            nosol();
        for (auto i : g[x])
            if (!tag[i]) {
                ++cnt;
                if (~si && si != siz[i])
                    nosol();
                if (si == -1)
                    si = siz[i];
            }
        if (cnt != 3)
            nosol();
        tag[x] = 1;
        for (auto i : g[x])
            if (!tag[i])
                DFS1(i);
        return;
    }; 
    DFS1(1);
    std::cout << "da" << '\n';
    return 0;
}

B - Distance on Triangulation

https://www.luogu.com.cn/problem/P6976

众(除了我)所周知 凸多边形的三角剖分可以转化为树上问题,故把树建出来;一个重要的点是要认识到这个树的意义。树上的点其实是没什么物理含义的;经过了某一个树上的点和经过了三角形上的任一点是等价的;也就是说,想要从某个点走到另一个点,肯定要经过对应的树上路径,但这不能反应实际距离什么的,只是能获取路径可能包含的点和必须包含的点(的超集),具体还是要最短路。

接着转化为点分治。但其实这里的点分治有点像序列分治:先跑一遍根节点(三个)在当前分治范围内的最短路,然后处理经过了这个点的询问,然后递归。容易发现三个点里有一些之前被算过了,且一定包含了这个分治范围,直接跳过就行了。

注意询问要写成整体二分那种动态下传的形式,只把被某个子树完全包含的询问传下去;不然复杂度是错的。以及这样就可以剪枝:发现询问数组空了就可以停了。直接 TLE to 200ms 了 😅

#include <bits/stdc++.h>
int main() {
#ifdef ONLINE_JUDGE
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr), std::cout.tie(nullptr);
#else
    std::freopen(".in", "r", stdin);
    std::freopen(".out", "w", stdout);
#endif
    int n;
    std::cin >> n;
    std::vector<int> deg(n + 1);
    std::vector<std::vector<int> > _g(n + 1), g(n + 1);
    for (int i = 1; i < n; ++i) {
        _g[i].push_back(i + 1), _g[i + 1].push_back(i);
        ++deg[i], ++deg[i + 1];
    }
    _g[1].push_back(n), _g[n].push_back(1);
    ++deg[1], ++deg[n];
    for (int i = 1, x, y; i <= n - 3; ++i) {
        std::cin >> x >> y;
        _g[x].push_back(y), _g[y].push_back(x);
        ++deg[x], ++deg[y];
    }
    int cnt = 0;
    std::vector<std::vector<int> > t(1);
    {
        std::queue<int> q;
        std::vector<int> inq(n + 1);
        for (int i = 1; i <= n; ++i)
            if (deg[i] == 2)
                q.push(i);
        std::map<std::pair<int, int>, int> st;
        for (; !q.empty(); ) {
            int x = q.front();
            q.pop(), inq[x] = 1;
            if (deg[x] == 2) {
                ++cnt, t.push_back({ x });
                for (auto i : _g[x])
                    if (!inq[i]) {
                        t[cnt].push_back(i);
                    }
                std::sort(t[cnt].begin(), t[cnt].end());
                for (int a = 0; a < 2; ++a)
                    for (int b = a + 1; b < 3; ++b) {
                        std::pair<int, int> now(t[cnt][a], t[cnt][b]);
                        if (st.count(now)) {
                            int to = st[now];
                            // printf("add %d <-> %d\n", to, cnt);
                            g[cnt].push_back(to), g[to].push_back(cnt);
                        }
                        else
                            st[now] = cnt;
                    }
            }
            for (auto i : _g[x])
                if (--deg[i] == 2)
                    q.push(i);
        }
    }
    int q;
    std::cin >> q;
    std::vector<int> res(q + 1, 0x3f3f3f3f);
    std::vector<std::tuple<int, int, int> > u(q);
    for (int i = 1, x, y; i <= q; ++i)
        std::cin >> x >> y, u[i - 1] = { x, y, i };
    int tik = 0;
    std::vector<int> siz(cnt + 1), mx(cnt + 1), p, now(n + 1), p1;
    std::vector<int> dis(n + 1), tag(n + 1), vis(n + 1), faa(n + 1);
    std::function<void(int, int)> DFS1 = [&](int x, int fa) {
        siz[x] = 1, mx[x] = 0;
        p.push_back(x);
        for (auto i : t[x])
            now[i] = tik, p1.push_back(i);
        for (auto i : g[x])
            if (i != fa && !tag[i]) {
                DFS1(i, x);
                siz[x] += siz[i];
                mx[x] = std::max(mx[x], siz[i]);
            }
        return;
    };
    auto findrt = [&](int x) {
        p.clear(), p1.clear(), DFS1(x, -1);
        int n = (int)p.size();
        for (auto i : p)
            if (mx[i] <= n / 2 && n - siz[i] <= n / 2)
                return i;
        return -1;
    };
    auto BFS = [&](int x) {
        std::queue<int> q;
        for (auto i : p1)
            dis[i] = 0x3f3f3f3f;
        q.push(x), dis[x] = 0;
        for (; !q.empty(); ) {
            int x = q.front();
            q.pop();
            for (auto i : _g[x])
                if (dis[i] == 0x3f3f3f3f && now[i] == tik)
                    dis[i] = dis[x] + 1, q.push(i);
        }
        return;
    };
    std::function<void(int, int, int)> calc = [&](int x, int fa, int rt) {
        for (auto i : t[x])
            faa[i] = rt;
        for (auto i : g[x])
            if (i != fa && !tag[i])
                calc(i, x, rt);
        return;
    };
    std::vector<std::vector<std::tuple<int, int, int> > > tmp(n + 1);
    std::function<void(int, std::vector<std::tuple<int, int, int> >)> DFS = [&](int x, std::vector<std::tuple<int, int, int> > q) {
        if (q.empty())
            return;
        ++tik, x = findrt(x);
        for (auto i : g[x])
            calc(i, x, i);
        for (auto k : t[x])
            if (!vis[k]) {
                vis[k] = 1, BFS(k);
                for (auto [u, v, id] : q)
                    res[id] = std::min(res[id], dis[u] + dis[v]);
            }
        for (auto [u, v, id] : q)
            if (faa[u] == faa[v])
                tmp[faa[u]].emplace_back(u, v, id);
        std::vector<std::tuple<int, int, int> >().swap(q);
        tag[x] = 1;
        for (auto i : g[x])
            if (!tag[i])
                DFS(i, std::move(tmp[i]));
        return;
    };
    DFS(1, std::move(u));
    for (int i = 1; i <= q; ++i)
        std::cout << res[i] << '\n';
    return 0;
}

C - Shopping

https://www.luogu.com.cn/problem/P6326

意识到连通块 DP 也是可以放在点分上做的,令 \(f_{u,i}\) 表示在(点分时的)子树 \(u\) 中,\(u\) 选了至少一个,共用了 \(i\) 体积的最大价值。剩下就是一个树上的多重 + 01 背包;发现这个 01 背包不满足可以优化的形式,所以考虑树上背包序列化,转换成序列上多重背包问题;不知道真实数据强度,但我认为应该用 单调队列优化多重背包

注意树背包序列化的细节其实是有点多的。可能需要一些邪思。

#include <bits/stdc++.h>
const int inf = 0x3f3f3f3f;
int main() {
#ifdef ONLINE_JUDGE
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr), std::cout.tie(nullptr);
#else
    std::freopen(".in", "r", stdin);
    std::freopen(".out", "w", stdout);
#endif
    int T;
    for (std::cin >> T; T--; ) {
        int n, m;
        std::cin >> n >> m;
        std::vector<std::vector<int> > g(n + 1);
        std::vector<int> v(n + 1), w(n + 1), c(n + 1);
        for (int i = 1; i <= n; ++i)
            std::cin >> w[i];
        for (int i = 1; i <= n; ++i)
            std::cin >> v[i];
        for (int i = 1; i <= n; ++i)
            std::cin >> c[i];
        for (int i = 1, x, y; i < n; ++i) {
            std::cin >> x >> y;
            g[x].push_back(y), g[y].push_back(x);
        }
        std::vector<int> siz(n + 1), mx(n + 1), tag(n + 1), p;
        std::function<void(int, int)> DFS1 = [&](int x, int fa) {
            p.push_back(x);
            siz[x] = 1, mx[x] = 0;
            for (auto i : g[x])
                if (i != fa && !tag[i]) {
                    DFS1(i, x);
                    siz[x] += siz[i];
                    mx[x] = std::max(mx[x], siz[i]);
                }
            return;
        };
        int now = 0;
        std::vector<int> nex(n + 1), tab(n + 1);
        auto findrt = [&](int x) {
            p.clear(), DFS1(x, -1);
            int n = (int)p.size();
            for (auto i : p)
                if (mx[i] <= n / 2 && n - siz[i] <= n / 2)
                    return i;
            assert(0);
            return -1;
        };
        std::function<void(int, int)> DFS2 = [&](int x, int fa) {
            tab[++now] = x;
            for (auto i : g[x])
                if (i != fa && !tag[i])
                    DFS2(i, x);
            nex[x] = now;
            return;
        };
        int res = -inf;
        std::function<void(int)> DFS = [&](int x) {
            x = findrt(x), now = 0, DFS2(x, -1);
            std::vector<std::vector<int> > f(now + 1, std::vector<int> (m + 1, -inf));
            for (int i = 1; i <= c[x] && i * v[x] <= m; ++i)
                f[1][i * v[x]] = i * w[x];
            for (int i = 1; i < now; ++i) {
                int V = v[tab[i + 1]], W = w[tab[i + 1]], C = c[tab[i + 1]];
                for (int b = 0; b < V; ++b) {
                    std::vector<int> q;
                    int h = 0, t = -1;
                    for (int a = 0; a * V + b <= m; ++a) {
                        for (; h <= t && a - q[h] > C; ++h);
                        if (h <= t && f[i][q[h] * V + b] != -inf)
                            f[i + 1][a * V + b] = std::max(f[i + 1][a * V + b], f[i][q[h] * V + b] + (a - q[h]) * W);
                        for (; h <= t && f[i][a * V + b] - a * W >= f[i][q[t] * V + b] - q[t] * W; --t);
                        q.resize(++t + 1), q[t] = a;
                    }
                }
                int to = nex[tab[i + 1]];
                for (int j = 0; j <= m; ++j)
                    f[to][j] = std::max(f[to][j], f[i][j]);
            }
            res = std::max(res, *std::max_element(f[now].begin(), f[now].end()));
            std::vector<std::vector<int> >().swap(f);
            tag[x] = 1;
            for (auto i : g[x])
                if (!tag[i])
                    DFS(i);
            return;
        };
        DFS(1);
        std::cout << res << '\n';
    }
    return 0;
}

D - 加油站

https://www.luogu.com.cn/problem/P10805

点分治后分为 \(u\to rt\)\(rt\to v\) 两个部分考虑。前者只需要倍增找到第一次加油的点即可;后者需要对于每个点作为加油站的情况,统计起点个数。

具体地,需要分类讨论:若 \(d(rt,v)<k\),说明上次加油一定不在当前子树内;在根节点上登记的加油站中统计走得到 \(fa_v\) 但走不到 \(v\) 的,就可以找到 \(fa_v\) 作为加油站对应的起点个数。对 \(d(rt,v)\ge k\),只需要倍增找到上一次加油的点即可找到 \(fa_v\) 的答案。

说着很轻巧,实现着很恶心。摆了。


E - 首都

https://www.luogu.com.cn/problem/P7215

启示我们点分治处理的问题不一是与『树结构』强相关者;也可以仅利用点分治划分出来的块,例如『若超出当前块,则一定不优』之类的。

本题的该性质通过分讨是好证的;所以直接每个块内暴力即可。复杂度 \(O(n\log n)\)

#include <bits/stdc++.h>
int main() {
#ifdef ONLINE_JUDGE
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr), std::cout.tie(nullptr);
#else
    std::freopen(".in", "r", stdin);
    std::freopen(".out", "w", stdout);
#endif
    int n, k;
    std::cin >> n >> k;
    std::vector<std::vector<int> > g(n + 1), t(k + 1);
    for (int i = 1, x, y; i < n; ++i) {
        std::cin >> x >> y;
        g[x].push_back(y), g[y].push_back(x);
    }
    std::vector<int> col(n + 1), mx(n + 1), siz(n + 1);
    for (int i = 1; i <= n; ++i) {
        std::cin >> col[i];
        t[col[i]].push_back(i);
    }
    std::vector<int> tag(n + 1), p, tik(n + 1), faa(n + 1), vis(n + 1), book(k + 1);
    std::function<void(int, int)> DFS1 = [&](int x, int fa) {
        siz[x] = 1, mx[x] = 0;
        p.push_back(x), tik[x] = 1;
        for (auto i : g[x])
            if (!tag[i] && i != fa) {
                DFS1(i, x);
                siz[x] += siz[i];
                mx[x] = std::max(mx[x], siz[i]);
            }
        return;
    };
    auto findrt = [&](int x) {
        p.clear(), DFS1(x, -1);
        int n = (int)p.size();
        for (auto i : p)
            if (mx[i] <= n / 2 && n - siz[i] <= n / 2)
                return i;
        assert(0);
        return -1;
    };
    std::function<void(int)> DFS2 = [&](int x) {
        for (auto i : g[x])
            if (!tag[i] && i != faa[x])
                faa[i] = x, DFS2(i);
        return;
    };
    int res = 0x3f3f3f3f;
    std::function<void(int)> DFS = [&](int x) {
        x = findrt(x);
        faa[x] = -1, DFS2(x);
        std::queue<int> q;
        q.push(col[x]), book[col[x]] = 1;
        int cnt = 0;
        for (; !q.empty(); ) {
            int f = q.front();
            q.pop();
            for (auto i : t[f])
                if (tik[i]) 
                    for (int p = i; ~p && !vis[p]; p = faa[p]) {
                        vis[p] = 1;
                        if (!book[col[p]]) {
                            if (++cnt >= res)
                                goto outo;
                            q.push(col[p]), book[col[p]] = 1;
                        }
                    }
                else
                    goto outo;
        }
        res = cnt;
        // printf("x = %d: cnt = %d\n", x, cnt);
    outo:
        for (auto i : p)
            vis[i] = tik[i] = book[col[i]] = 0;
        tag[x] = 1;
        for (auto i : g[x])
            if (!tag[i])
                DFS(i);
        return;
    };
    DFS(1);
    std::cout << res << '\n';    
    return 0;
}

F - 开店

https://www.luogu.com.cn/problem/P3241

转化问题,原问题即求解权值在 \([L,R]\) 间的元素到 \(u\) 的距离和。

一般求解距离的方法是找到 LCA 后计算,考虑如果不利用树上 LCA,而是利用点分树上 LCA 如何计算。分讨易证得点分树上 LCA 一定在两点路径上。

储存点分树上每个点到其每个祖先的距离,就可以方便地计算任意两点间距离。接下来处理原问题的弱化:对于每个 \(u\) 求解其到树上所有点距离和。显然这是一个类似换根的问题,随便做即可。

现在需要在线地解决原问题。可以用 vector 存储这一层所有点的权值与深度信息,按权值排序后二分查询。复杂度 \(O(n\log^2 n)\)。实现的时候应该可以注意到原题限制点度数对复杂度的保证。

打起来的时候感觉很史,但实际上调得比大多数题快。

大概懂得为什么经常看大佬写游记做到史题的实现预期都是『一遍过』了。

#include <bits/stdc++.h>
const long long inf = 1e18;
const int maxn = 1.5e5 + 5;
std::vector<std::pair<int, int> > g[maxn];
int top[maxn], siz[maxn], fa[maxn], son[maxn], dep[maxn], dis[maxn];
void DFS1(int x) {
    siz[x] = 1;
    for (auto [i, w] : g[x])
        if (i != fa[x]) {
            fa[i] = x;
            dep[i] = dep[x] + 1, dis[i] = dis[x] + w;
            DFS1(i);
            siz[x] += siz[i];
            if (siz[i] > siz[son[x]])
                son[x] = i;
        }
    return;
}
void DFS2(int x) {
    if (son[x])
        top[son[x]] = top[x], DFS2(son[x]);
    for (auto [i, w] : g[x])
        if (i != fa[x] && i != son[x])
            top[i] = i, DFS2(i);
    return;
}
int ask(int x, int y) {
    int sx = x, sy = y;
    for (; top[x] != top[y]; x = fa[top[x]])
        if (dep[top[x]] < dep[top[y]])
            std::swap(x, y);
    x = dep[x] < dep[y] ? x : y;
    return dis[sx] + dis[sy] - 2 * dis[x];
}
int main() {
#ifdef ONLINE_JUDGE
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr), std::cout.tie(nullptr);
#else
    std::freopen(".in", "r", stdin);
    std::freopen(".out", "w", stdout);
#endif
    int n, q, A;
    std::cin >> n >> q >> A;
    std::vector<int> c(n + 1);
    for (int i = 1; i <= n; ++i)
        std::cin >> c[i];
    for (int i = 1, x, y, w; i < n; ++i) {
        std::cin >> x >> y >> w;
        g[x].emplace_back(y, w), g[y].emplace_back(x, w);
    }
    DFS1(1), DFS2(1);
    std::vector<int> mx(n + 1), siz(n + 1), p, tag(n + 1);
    std::function<void(int, int)> DFS1 = [&](int x, int fa) {
        siz[x] = 1, mx[x] = 0;
        p.push_back(x);
        for (auto [i, w] : g[x])
            if (!tag[i] && i != fa) {
                DFS1(i, x);
                siz[x] += siz[i];
                mx[x] = std::max(mx[x], siz[i]);
            }
        return;
    };
    auto findrt = [&](int x) {
        p.clear(), DFS1(x, -1);
        int n = (int)p.size();
        for (auto i : p)
            if (mx[i] <= n / 2 && n - siz[i] <= n / 2)
                return i;
        assert(0);
        return -1;
    };
    std::vector<int> dis(n + 1);
    std::vector<std::array<std::vector<std::pair<int, long long> >, 3> > t(n + 1);
    std::function<void(int, int, std::vector<std::pair<int, long long> >&)> DFS2 = [&](int x, int fa, std::vector<std::pair<int, long long> > &t) {
        t.emplace_back(c[x], dis[x]);
        for (auto [i, w] : g[x])
            if (!tag[i] && i != fa) {
                dis[i] = dis[x] + w;
                DFS2(i, x, t);
            }
        return;
    };
    std::vector<std::pair<int, int> > to(n + 1);
    std::function<std::pair<int, int> (int)> DFS = [&](int x) {
        x = findrt(x);
        int ret = dis[x];
        for (int j = 0; j < (int)g[x].size(); ++j) {
            auto [i, w] = g[x][j];
            if (!tag[i]) {
                dis[i] = w;
                DFS2(i, x, t[x][j]);
                std::sort(t[x][j].begin(), t[x][j].end());
                // printf("[%d, %d]: %d(%lld)", x, j, t[x][j][0].first, t[x][j][0].second);
                for (int k = 1; k < (int)t[x][j].size(); ++k) {
                    t[x][j][k].second += t[x][j][k - 1].second;
                    // printf(" %d(%lld)", t[x][j][k].first, t[x][j][k].second);
                }
                // puts("");
            }
        }
        tag[x] = 1;
        for (int j = 0; j < (int)g[x].size(); ++j) {
            auto [i, w] = g[x][j];
            if (!tag[i]) {
                auto [rt, d] = DFS(i);
                to[rt] = { x, j };
            }
        }
        return std::make_pair(x, ret);
    };
    int rt = DFS(1).first;
    std::vector<std::vector<int> > fd(n + 1);
    for (int i = 1; i <= n; ++i) {
        int now = i;
        for (;; now = to[now].first) {
            fd[i].push_back(ask(now, i));
            if (now == rt)
                break;
        }
    }
    for (int u, a, b; q--; ) {
        static long long la = 0ll;
        std::cin >> u >> a >> b;
        int l = std::min((a + la) % A, (b + la) % A), r = std::max((a + la) % A, (b + la) % A);
        // printf("ask %d, [%d, %d]\n", u, l, r);
        auto ask = [&](int x, int j) {
            auto res(0ll);
            int cnt = 0, p = 0;
            p = std::lower_bound(t[x][j].begin(), t[x][j].end(), std::make_pair(l, -1ll)) - t[x][j].begin();
            cnt -= p;
            if (p != 0)
                res -= t[x][j][p - 1].second;
            p = std::upper_bound(t[x][j].begin(), t[x][j].end(), std::make_pair(r, inf)) - t[x][j].begin();
            cnt += p;
            if (p != 0)
                res += t[x][j][p - 1].second;
            return std::make_pair(res, cnt);
        };
        la = ask(u, 0).first + ask(u, 1).first + ask(u, 2).first;
        // int U = u;
        for (auto d = ++fd[u].begin(); u != rt; ++d) {
            auto [fa, k] = to[u];
            // fprintf(stderr, "u = %d, fa = %d, d = %d\n", U, fa, *d);
            for (int j = 0; j < 3; ++j)
                if (j != k) {
                    auto [len, cnt] = ask(fa, j);
                    la += len + (long long)cnt * *d;
                }
            if (c[fa] >= l && c[fa] <= r)
                la += *d;
            u = fa;
        }
        std::cout << la << '\n';
    }
    return 0;
}

言论