7. 子树边权平方和


这是别人问我的题,所以没有题目链接,名字也是我随便起的。

有一棵包含 \(n\) 个节点的树,节点编号从 \(1\)\(n\),以 \(1\) 为根,每条边都有一个权值。给你 \(m\) 次询问,每次询问一个子树,在子树中对每种边权的值 \(c\) 统计出现次数 \(cnt_{c}\),求 \(\sum(c\cdot cnt_c)^2\)

\(n,m\) 以及边权范围均为 \(1\times 10^5\)

容易发现可以将边权下放到点,并且这个平方和很好维护,增删一个权值都可以 \(O(1)\) 完成。那么可以用 dsu on tree 来做,先预处理出所有节点的最大子树,然后 dfs 的时候先算所有小子树再算大子树,最后暴力合并小子树即可。时间复杂度 \(O(n\log n)\)

#include 
using namespace std;
using ll = long long;
using pii = pair;
const int maxn = 1e5 + 5;
ll weight[maxn], ans[maxn];
vector g[maxn], id[maxn];
int sz[maxn], son[maxn];
void dfs0(int u) {
    sz[u] = 1;
    for (auto v : g[u]) {
        dfs0(v);
        sz[u] += sz[v];
        if (sz[v] > sz[son[u]])
            son[u] = v;
    }
}
int cnt[maxn], vis[maxn];
ll sum;
void clear(int u) {
    sum -= (weight[u] * cnt[weight[u]]) * (weight[u] * cnt[weight[u]]);
    cnt[weight[u]]--;
    sum += (weight[u] * cnt[weight[u]]) * (weight[u] * cnt[weight[u]]);
    for (auto v : g[u])
        clear(v);
}
void dfs(int u, int op) {
    for (auto v : g[u]) {
        if (v == son[u])
            continue;
        dfs(v, 0);
    }
    if (son[u]) {
        dfs(son[u], 1);
    }
    for (auto v : g[u]) {
        if (v == son[u])
            continue;
        dfs(v, 1);
    }
    if (!vis[u]) {
        for (auto x : id[u]) {
            ans[x] = sum;
        }
        vis[u] = 1;
    }
    sum -= (weight[u] * cnt[weight[u]]) * (weight[u] * cnt[weight[u]]);
    cnt[weight[u]]++;
    sum += (weight[u] * cnt[weight[u]]) * (weight[u] * cnt[weight[u]]);
    if (!op) {
        clear(u);
    }
}
void solve() {
    int n, m, c, q;
    cin >> n >> m >> c;
    for (int i = 1, u, v, w; i < n; ++i) {
        cin >> u >> v >> w;
        g[u].push_back(v);
        weight[v] = w;
    }
    for (int i = 1, x; i <= m; ++i) {
        cin >> x;
        id[x].push_back(i);
    }
    dfs0(1);
    dfs(1, 1);
    for (int i = 1; i <= m; ++i) {
        cout << ans[i] << '\n';
    }
}
int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    solve();
    return 0;
}