Codeforces 609E (Kruskal求最小生成树+树上倍增求LCA)

本文介绍了一种求解特定图论问题的方法:对于给定的无向连通带权图,找到每条边作为生成树一部分时该生成树的最小可能大小。利用Kruskal算法构建最小生成树,并通过树上倍增法高效地找出每条边对应的最优解。
部署运行你感兴趣的模型镜像

题面

传送门
题目大意:
给定一个无向连通带权图G,对于每条边 (u,v,w) ( u , v , w ) ,求包含这条边的生成树大小的最小值

分析

包含这条边的生成树的大小如何表示呢?
先求出整张图的最小生成树大小tlen,对于每一条边 (u,v,w) ( u , v , w ) ,我们最小生成树中去掉树上从u到v的路径上权值最大,最大值为mlen的一条边,再加上w,得到的一定是包含这条边的生成树大小的最小值 tlenmlen+w t l e n − m l e n + w

最小生成树大小tlen可用kruskal算法在 O(mlog2m) O ( m l o g 2 m ) 时间内求出
那么问题转化为求mlen,可用树上倍增法求解

树上倍增法的好处是在求LCA的同时可以维护更多的附加信息
在求LCA的过程中设fa[i][j]表示i的 2j 2 j 辈祖先
可写出公式

fa[i][j]=fa[fa[i][j1]][j1] f a [ i ] [ j ] = f a [ f a [ i ] [ j − 1 ] ] [ j − 1 ]

(即i的 2j 2 j 辈祖先是i的 2j1 2 j − 1 辈祖先的 2j1 2 j − 1 辈祖先)
同理可写出最大长度
mlen[i][j]=max(mlen[i][j1],mlen[mlen[i][j1]][j1]) m l e n [ i ] [ j ] = m a x ( m l e n [ i ] [ j − 1 ] , m l e n [ m l e n [ i ] [ j − 1 ] ] [ j − 1 ] )

查询时类似LCA的查询即可,详情见代码

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<cmath>
#define maxn 200005
#define maxm 200005
#define maxlog 32
using namespace std;
int n,m;
inline int qread(){
    int x=0,sign=1;
    char c=getchar();
    while(c<'0'||c>'9'){
        if(c=='-') sign=-1;
        c=getchar();
    }
    while(c>='0'&&c<='9'){
        x=x*10+c-'0';
        c=getchar();
    }
    return x*sign;
}
struct edge{
    int from;
    int to;
    int len;
    int next;
    int index;
    edge(){

    }
    edge(int x,int y,int z,int i){
        from=x;
        to=y;
        len=z;
        index=i;
    }
    friend bool operator <(edge x,edge y){
        return x.len<y.len;
    }
};
edge G[maxm*2],MST[maxm*2];
int head[maxn];
int size=0;
void add_edge(int u,int v,int w){
    size++;
    MST[size].from=u;
    MST[size].to=v;
    MST[size].len=w;
    MST[size].next=head[u];
    head[u]=size;
}
int fset[maxn];
void set_init(){
    for(int i=1;i<=n;i++) fset[i]=i;
}
int find(int x){
    if(fset[x]==x) return x;
    else{
        fset[x]=find(fset[x]);
        return fset[x];
    }
}
long long kruskal(){
    long long ans=0;
    sort(G+1,G+1+m);
    for(int i=1;i<=m;i++){
        int fx=find(G[i].from);
        int fy=find(G[i].to);
        if(fx!=fy){
            add_edge(G[i].from,G[i].to,G[i].len);
            add_edge(G[i].to,G[i].from,G[i].len);
            fset[fx]=fy;
            ans+=G[i].len; 
        }
    }
    return ans;
}

int deep[maxn],fa[maxn][maxlog];
long long mlen[maxn][maxlog];
int log2n;
void lca_init(){
    queue<int>q;
    q.push(1);
    deep[1]=1; //初始化深度
    while(!q.empty()){
        int x=q.front();
        q.pop();
        for(int i=head[x];i;i=MST[i].next){//MST表示最小生成树的边
            int y=MST[i].to;
            if(deep[y]) continue;
            deep[y]=deep[x]+1;
            fa[y][0]=x;//fa和mlen的初始值
            mlen[y][0]=MST[i].len;
            for(int j=1;j<=log2n;j++){//倍增初始化
                fa[y][j]=fa[fa[y][j-1]][j-1];
                mlen[y][j]=max(mlen[y][j-1],mlen[fa[y][j-1]][j-1]);
            }
            q.push(y);
        }
    }
}
long long lca_query(int x,int y){
    if(deep[x]>deep[y]) swap(x,y);
    long long maxl=0;
    for(int i=log2n;i>=0;i--){//先将x和y调整到同一深度
        if(deep[fa[y][i]]>=deep[x]){
            maxl=max(maxl,mlen[y][i]);//y上升同时更新maxl
            y=fa[y][i];
        }
    }
    if(x==y) return maxl;//如果LCA(x,y)=x,直接返回
    for(int i=log2n;i>=0;i--){//x,y同时上升,直到差一条边相遇
        if(fa[x][i]!=fa[y][i]){
            maxl=max(maxl,max(mlen[x][i],mlen[y][i]));
            x=fa[x][i];
            y=fa[y][i];
        }
    }
    maxl=max(maxl,max(mlen[x][0],mlen[y][0]));//最后再更新一次
    return maxl;
}
long long ans[maxm];//便于按输入顺序输出
int main(){
    int s,t,r;
    n=qread();
    m=qread();
    for(int i=1;i<=m;i++){
        s=qread();
        t=qread();
        r=qread();
        G[i]=edge(s,t,r,i);
    }
    set_init();
    long long tlen=kruskal();
    log2n=log2(n)+1;
    lca_init();
    for(int i=1;i<=m;i++){
        ans[G[i].index]=tlen+(long long)G[i].len-(long long)lca_query(G[i].from,G[i].to);//求生成树大小的最小值
    }
    for(int i=1;i<=m;i++){
        printf("%I64d\n",ans[i]);
    }
}

您可能感兴趣的与本文相关的镜像

Yolo-v8.3

Yolo-v8.3

Yolo

YOLO(You Only Look Once)是一种流行的物体检测和图像分割模型,由华盛顿大学的Joseph Redmon 和Ali Farhadi 开发。 YOLO 于2015 年推出,因其高速和高精度而广受欢迎

### 关于 CodeForces 892E 的解题思路分析 #### 使用可撤销并查集解决最小生成树中的边集合验证问题 针对给定的无向图以及多个询问,每个询问涉及一组特定的边,并要判断这组边能否同时存在于某棵最小生成树中。此问题可以通过结合Kruskal算法构建最小生成树的过程来解,在这一过程中利用到的是按照权重升序排列后的边逐步加入至森林结构之中[^1]。 为了高效处理多次查询而不影响后续操作的结果,引入了带有回溯功能的数据结构——即所谓的“可撤销并查集”。这种特殊形式的并查集允许执行合并(union)的同时记录下每一次变动以便之后能够恢复原状;当完成一次查询判定后即可通过一系列反向动作使数据结构回到初始状态,从而不影响其他独立事件的发生逻辑[^3]。 具体实现方法如下: - 将所有的边依据其权重从小到大排序; - 对每一个询问所涉及到的边也做同样的预处理; - 开始遍历已排序好的全局边列表,每当遇到属于当前待检验询问范围内的边时,则尝试将其纳入现有连通分量内; - 如果发现形成环路则说明该询问无法满足条件; - 同样地,任何不属于当前询问但同样处于相同权值下的其它边也应该被考虑进来以确保最终形成的MST是最优解的一部分; - 完成一轮测试后记得清除所有临时更改使得系统重置为未受干扰的状态准备迎接下一个挑战。 ```cpp #include <bits/stdc++.h> using namespace std; struct Edge { int u, v; }; class DSUWithRollback { public: vector<int> parent, rank, historyParent, historyRank; void init(int n){ parent.resize(n); iota(parent.begin(), parent.end(), 0); // Fill with identity mapping. rank.assign(n, 0); historyParent.clear(); historyRank.clear(); } int findSet(int i) {return (parent[i]==i)?i:(findSet(parent[i]));} bool isSameSet(int i, int j){ return findSet(i)==findSet(j);} void unionSets(int i, int j){ if (!isSameSet(i,j)){ historyParent.push_back(findSet(i)); historyParent.push_back(findSet(j)); historyRank.push_back(rank[findSet(i)]); historyRank.push_back(rank[findSet(j)]); int x=findSet(i), y=findSet(j); if (rank[x]>rank[y]) swap(x,y); parent[x]=y; if (rank[x]==rank[y]) ++rank[y]; } } void rollback(){ while(!historyParent.empty()){ parent[historyParent.back()]=historyParent.back(); historyParent.pop_back(); rank[historyParent.back()] = historyRank.back(); historyParent.pop_back(); historyRank.pop_back(); } } }; ``` 上述代码展示了如何创建一个支持撤销机制的并查集类`DSUWithRollback`,它可以在不破坏原有连接关系的前提下安全地进行节点间的联合与查找操作。此外还提供了用于追踪变化历史的方法,方便在必要时候撤消最近的一系列更动。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值