F - MST Query

F - MST Query

Problem Statement

You are given a weighted undirected connected graph $G$ with $N$ vertices and $N-1$ edges, where vertices are numbered $1$ to $N$ and edges are numbered $1$ to $N-1$. Edge $i$ connects vertices $a_i$ and $b_i$ with a weight of $c_i$.

You are given $Q$ queries to process sequentially. The $i$-th query is described as follows:

  • You are given integers $u_i, v_i, w_i$. Add an edge with weight $w_i$ between vertices $u_i$ and $v_i$ in $G$. Then, print the sum of the weights of the edges in a minimum spanning tree of $G$.

Constraints

  • $2 \leq N \leq 2 \times 10^5$
  • $1 \leq Q \leq 2 \times 10^5$
  • $1 \leq a_i < b_i \leq N$
  • $1 \leq u_i < v_i \leq N$
  • $1 \leq c_i, w_i \leq 10$
  • The graph is connected before processing the queries.
  • All input values are integers.

Input

The input is given from Standard Input in the following format:

$N$ $Q$
$a_1$ $b_1$ $c_1$
$\vdots$
$a_{N-1}$ $b_{N-1}$ $c_{N-1}$
$u_1$ $v_1$ $w_1$
$\vdots$
$u_Q$ $v_Q$ $w_Q$

Output

Print $Q$ lines. The $i$-th line should contain the answer to the $i$-th query.


Sample Input 1

4 4
1 2 6
2 3 5
2 4 4
1 3 3
1 2 3
1 4 10
3 4 1

Sample Output 1

12
10
10
7

Here is the graph after adding the edge for each query. The edges included in the minimum spanning tree are colored red.


Sample Input 2

8 6
1 8 8
1 6 10
1 5 8
2 6 6
6 7 6
1 3 9
2 4 7
1 3 4
1 6 7
3 4 6
1 5 1
7 8 4
3 5 3

Sample Output 2

49
46
45
38
34
33

 

解题思路

  容易想到可以动态维护最小生成树,对于每个询问 $(u,v,w)$,当插入这条边时必然会形成一个环,为了使得生成树最小,则需要在树中找到 $u$ 到 $v$ 这条路径上最大边权对应的边,如果最大边权比 $w$ 大则删除这条边,再插入边 $(u,v)$。LCT 可以实现上述的操作,但我没学过不会

  注意到边权非常小,我们可以换一种思路去思考这个问题。上面的做法是动态维护大小为 $n-1$ 的边集,而暴力的做法是直接往边集里加新边,每次跑一遍 Kruskal 求 MST。回想 Kruskal 算法,本质是按边权从小到大把不会构成环的边加到边集中,最后得到的 MST 的权重就是 $\sum{i \cdot c_i}$,其中 $c_i$ 表示边集中边权 $i$ 的数量。现在边权最大只有 $10$,显然可以直接去暴力计算这个结果,但问题是如何知道 $c_i$。

  每往边集加入一条边后,连通块的数量就会减 $1$。考虑把不构成环的权值小于 $i$ 的边都加入后的连通块数量,记为 $\text{cnt}[i-1]$,再把不构成环的权值为 $i$ 的边加入后的连通块数量,记为 $\text{cnt}[i]$,那么边集中权值为 $i$ 的边的数量就是 $c_i = \text{cnt}[i-1] - \text{cnt}[i]$。

  为此我们需要维护 $10$ 个并查集,分别维护加入权值不超过 $i$ 且不构成环的边所构成的连通块。每要次插入边 $(u,v,w)$ 时,因此遍历 $w \sim 10$ 所对应的并查集,如果 $u$ 和 $v$ 不在同一个连通块内,则进行合并,对应并查集的连通块数量减 $1$。

  AC 代码如下,时间复杂度为 $O((n+m) \cdot W)$:

#include <bits/stdc++.h>
using namespace std;

typedef long long LL;

const int N = 2e5 + 5, M = 15;

int fa[M][N], cnt[M];

int find(int *fa, int x) {
    return fa[x] == x ? fa[x] : fa[x] = find(fa, fa[x]);
}

int main() {
    int n, m;
    scanf("%d %d", &n, &m);
    for (int i = 0; i <= 10; i++) {
        iota(fa[i], fa[i] + n + 1, 0);
        cnt[i] = n;
    }
    for (int i = 0; i < n - 1; i++) {
        int u, v, w;
        scanf("%d %d %d", &u, &v, &w);
        for (int j = w; j <= 10; j++) {
            int a = find(fa[j], u), b = find(fa[j], v);
            if (a != b) {
                fa[j][a] = b;
                cnt[j]--;
            }
        }
    }
    while (m--) {
        int u, v, w;
        scanf("%d %d %d", &u, &v, &w);
        for (int j = w; j <= 10; j++) {
            int a = find(fa[j], u), b = find(fa[j], v);
            if (a != b) {
                fa[j][a] = b;
                cnt[j]--;
            }
        }
        int ret = 0;
        for (int i = 1; i <= 10; i++) {
            ret += (cnt[i - 1] - cnt[i]) * i;
        }
        printf("%d\n", ret);
    }
    
    return 0;
}

 

参考资料

  Editorial - Tokio Marine & Nichido Fire Insurance Programming Contest 2024(AtCoder Beginner Contest 355):https://atcoder.jp/contests/abc355/editorial/10072

  AtCoder Beginner Contest 355 - ~Lanly~ - 博客园:https://www.cnblogs.com/Lanly/p/18213969#f---mst-query-abc355-f

posted @ 2024-05-27 21:21  onlyblues  阅读(36)  评论(0编辑  收藏  举报
Web Analytics