最小度限制生成树

本文介绍了一种求解特定条件下最小生成树的问题——最小度限制生成树算法。该算法通过预处理图的连通性及最小生成树,进而逐步调整生成树结构,确保指定节点的度数不超过给定值,同时保持生成树权值最小。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最小度限制生成树

给定图G,求使得图中结点\(V_0\)的度不大于k的最小生成树。

设与v0相连的边集合是E0,|E0|=m,首先把v0去掉,对分裂后的每一个联通块都跑一遍最小生成树,得到边集合E1。一定存在一个方案,使得G的最小任意度限制生成树只用到E1中的和E0中的元素。证明在黑书p301,关键在于一定有一条在最小生成树上的路径连接不属于E1的边(i, j),并且这个路径中的所有边都小于(i, j)。

所以现在只需要考虑变成树后的图了。设已知最小i-1度生成树,一定可以通过加入一条边,删去形成的环上的另一条边,来变成最小i度生成树(因为至少要选一条和v0相连的边)。暴力找最优的操作,时间复杂度是\(O(n^2)\)。如果每次维护任意点到v0的路径上权值最大的边,那么找最优的边的操作和维护的操作时间复杂度都是\(O(n)\)

注意求最值及其位置的方法有两个。一是只保存位置,缺陷是初始值不好确认。而是同时保存最值和位置,但是较烦。

#include <map>
#include <cstdio>
#include <string>
#include <iostream>
#include <algorithm>
using namespace std;

typedef long long LL;
const LL maxn=2005, maxm=4e5+5;

LL n, m, k, root, tota, totb, ans;
struct Edge1{
    LL x, y, d;
    void set(LL a, LL b, LL c){ x=a; y=b; d=c; }
}a[maxm], b[maxm];  //以上关于前向星
bool cmp(const Edge1 &a, const Edge1 &b){ return a.d<b.d; }

//string stmp, stmp2;
//map<string, LL> ma;  //以上关于离散化

LL fa[maxn];
LL find(LL x){ return fa[x]==x?x:fa[x]=find(fa[x]); }  //以上关于并查集

LL fir[maxn], cnte;
struct Edge{
    LL to, next, v;
}edge[maxm];
void adde(LL x, LL y, LL z){
    Edge &e=edge[++cnte];
    e.to=y; e.next=fir[x]; e.v=z; fir[x]=cnte;
}  //以上邻接表

LL G[maxn][maxn], use[maxn];  //G:哪个边被ban掉了
LL pos1[maxn], pos2[maxn];  //路径上最大边的两个端点
LL Max[maxn];  //接上:没有必要记录边的编号,但用邻接矩阵却太浪费

void dfs(LL u, LL par){ LL v;
    for (LL i=fir[u]; i; i=edge[i].next){
        v=edge[i].to; if (v==par||G[u][v]) continue;
        Max[v]=max(Max[u], edge[i].v);
        if (edge[i].v>Max[u]) pos1[v]=v, pos2[v]=u;
        else pos1[v]=pos1[u], pos2[v]=pos2[u];
        dfs(v, u);
    }
}

int main(){
    scanf("%lld%lld%lld", &n, &m, &k); LL t1, t2, v;
    for (LL i=0; i<m; ++i){
        scanf("%lld%lld%lld", &t1, &t2, &v);
        if (t1&&t2) a[tota++].set(t1, t2, v);
        else b[totb++].set(t1, t2, v);
    }
    //去掉root,kruskal重建图
    sort(a, a+tota, cmp); sort(b, b+totb, cmp);
    for (LL i=1; i<=n; ++i) fa[i]=i;
    for (LL i=0; i<tota; ++i){
        if (find(a[i].x)==find(a[i].y)) continue;
        fa[find(a[i].x)]=find(a[i].y); ans+=a[i].d;
        adde(a[i].x, a[i].y, a[i].d); adde(a[i].y, a[i].x, a[i].d);
    }
    //加入与root相连的最小边
    for (LL i=0; i<totb; ++i){
        if (find(b[i].x)==find(b[i].y)) continue;
        fa[find(b[i].x)]=find(b[i].y); ans+=b[i].d;
        --k; use[i]=true;
        adde(b[i].x, b[i].y, b[i].d); adde(b[i].y, b[i].x, b[i].d);
    }
    while (k--){  //加几条边
        dfs(root, 0);
        LL tmp, maxm=0, maxp;
        for (LL i=0; i<totb; ++i){
            if (use[i]) continue;
            tmp=max(Max[b[i].x], Max[b[i].y]); tmp-=b[i].d;
            if (maxm<tmp) maxm=tmp, maxp=i;
        }  //找到最好的边
        if (!maxm) break;
        if (b[maxp].x) swap(b[maxp].x, b[maxp].y);
        G[pos1[b[maxp].y]][pos2[b[maxp].y]]=true;
        G[pos2[b[maxp].y]][pos1[b[maxp].y]]=true;
        adde(b[maxp].x, b[maxp].y, b[maxp].d);
        adde(b[maxp].y, b[maxp].x, b[maxp].d);
        ans=ans-maxm; use[maxp]=true;
    }
    printf("%lld\n", ans);
    return 0;
}

转载于:https://www.cnblogs.com/MyNameIsPc/p/8551326.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值