cf1108 F. MST Unification

题意:

给定无向带权图,每次操作选择一条边使其权加一,问至少几次操作后最小生成树唯一

思路:

法一:

首先随便用什么方法求出MST

对于某条不在MST中的边 \(e:u\stackrel{w}-v\),如果要把 \(e\) 加入MST中,就要删除 \(u\)\(v\) 在MST上的路径(这路径当然是唯一的)上的随便一条边。

这个操作不能改变MST的边权和。 \(u\)\(v\) 在MST上的路径上的最大边权为 \(W\),若 \(w<W\),加入 \(w\) 然后删除 \(W\) 就会使MST的边权和变小,若 \(w>W\) 则会变大,都是不合理的。所以只有一种情况 \(w=W\)

所以答案就是 边权等于两端点路径上的最大边权的边 的数量。用倍增求lca,同时记录 \(mx[x][k]\) 表示从节点 \(x\) 向根节点走 \(2^k\) 步经过的最大边权

int t, d[N], fa[N][20], mx[N][20];
void bfs() {
    t = log2(n) + 1;
    queue<int> q; q.push(1); d[1] = 1;
    while(q.size()) {
        int u = q.front(); q.pop();
        for(auto [v,w]: G[u]) if(!d[v]) {
            d[v] = d[u] + 1;
            fa[v][0] = u, mx[v][0] = w;
            for(int i = 1; i <= t; i++)
                fa[v][i] = fa[fa[v][i-1]][i-1],
                mx[v][i] = max(mx[v][i-1], mx[fa[v][i-1]][i-1]); //!!!
            q.push(v);
        }
    }
}
int lca(int u, int v) {
    int ans = 0;
    if(d[u] > d[v]) swap(u, v);
    for(int i = t; i >= 0; i--) if(d[fa[v][i]] >= d[u])
        ans = max(ans, mx[v][i]), v = fa[v][i];
    if(u == v) return ans;
    for(int i = t; i >= 0; i--) if(fa[u][i] != fa[v][i])
        ans = max({ans, mx[u][i], mx[v][i]}),
        u = fa[u][i], v = fa[v][i];
    return max({ans, mx[u][0], mx[v][0]}); //注意
}

void sol() {
    cin >> n >> m; vector<edge> e(m);
    for(auto &[u,v,w]: e) cin >> u >> v >> w;

    vector<edge> oth; //不在MST中的边
    
    iota(p, p + N, 0); //初始化并查集
    sort(all(e));
    for(auto &[u,v,w]: e)
        if(get(u) != get(v)) mer(u,v), G[u].pb({v,w}), G[v].pb({u,w});
        else oth.pb({u,v,w});
    
    bfs(); //初始化倍增
    int ans = 0; for(auto &[u,v,w]: oth)
        ans += w == lca(u, v);
    cout << ans;
}

法二:

考虑kruskal过程,\(S\) 表示所有权为 \(w\) 的边组成的集合。若 \(S\) 中的某条边的两端点在 \(S\) 之前已被连通,即已被某些边权 \(<w\) 的边连通,那么这条边就一点用都没有,不用考虑;否则,若某条边的两端点被 \(S\) 中的其他边连通,这条边就是冗余的,答案加一(实际操作时把它的权加一并弃用,因为后面也用不到)

void sol() {
    cin >> n >> m; vector<edge> e(m);
    for(auto &[u,v,w]: e) cin >> u >> v >> w;

    sort(all(e));

    iota(p, p + N, 0); //初始化并查集

    int i = 0, ans = 0;
    while(i < m) {
        int j = i; while(e[i].w == e[j].w && j < m) j++; //e[i]~e[j-1]边权相同
        for(int k = i; k < j; k++)
            if(get(e[k].u) != get(e[k].v))
                ans++;
        for(int k = i; k < j; k++)
            if(get(e[k].u) != get(e[k].v))
                mer(e[k].u, e[k].v), ans--;
        i = j;
    }
    cout << ans;
}
posted @ 2022-07-09 17:37  Bellala  阅读(55)  评论(0)    收藏  举报