欢迎光临
我们一直在努力

局域网(net)(信息学奥赛一本通- P1391)

【题目描述】

某个局域网内有n(n≤100)台计算机,由于搭建局域网时工作人员的疏忽,现在局域网内的连接形成了回路,我们知道如果局域网形成回路那么数据将不停的在回路内传输,造成网络卡的现象。因为连接计算机的网线本身不同,所以有一些连线不是很畅通,我们用f(i,j)表示i,j之间连接的畅通程度(f(i,j)≤1000),f(i,j)值越小表示i,j之间连接越通畅,f(i,j)为0表示i,j之间无网线连接。现在我们需要解决回路问题,我们将除去一些连线,使得网络中没有回路,并且被除去网线的Σf(i,j)最大,请求出这个最大值。

【输入】

第一行两个正整数n,k

接下来的k行每行三个正整数i,j,m表示i,j两台计算机之间有网线联通,通畅程度为m。

【输出】

一个正整数,Σf(i,j)的最大值。

【输入样例】

5 5
1 2 8
1 3 1
1 5 3
2 4 5
3 4 2

【输出样例】

8

1. 题目背景与分析

题目描述:

在一个局域网中,有N台计算机 (N<=100) 和K条网线。由于工作人员疏忽,网络中形成了回路(环),导致数据传输拥堵。每条网线有一个“通畅程度” f(i,j)(权值)。

我们需要拆除一些网线,使得:

  • 网络中没有回路(即变成树或森林结构)。

  • 被拆除网线的权值之和\\sum f(i,j)最大。

  • 核心思路转换:

    题目要求“被除去的边权和最大”,这在数学上等价于:

    被除去的权值=所有边的总权值- 留下的边权和

    为了让“被除去”的最大,我们必须让“留下”的最小。

    同时,留下的边必须保证连通且无环。

    结论:这道题本质上是求最小生成树。


    2. 解法一:Kruskal 算法 (推荐)

    为什么推荐 Kruskal?

  • 逻辑直接:Kruskal 算法在遍历边时,如果发现两个点已经连通(find(a) == find(b)),则跳过这条边。跳过的边正是我们要删除的边。我们只需要直接累加这些跳过的边权即可,无需计算总权值再减。

  • 天然支持重边与森林:Kruskal 不需要特殊处理重边(排序后自然处理),也不需要特殊处理非连通图(森林),它会自动处理所有边。

  • 完整代码 

    //求去除网线的联通度的总和最大,即不被连上的网线边权最大
    //所以这道题可以判断出就是求最小生成树(连接上所有计算机)
    //没有回路且边权最小
    /*
    //最佳方法kruskal 对边权从小到大排序后 先连接上的一定是边权较小的
    //所以如果有重边,一定是边权较大的没有被连上,即除去的网线的Σf(i,j)最大
    //复杂度:O(KlogK) 直接累加被抛弃的边,即为答案。
    //prim+邻接矩阵很麻烦,因为邻接矩阵不好保存重边,我们必须先算出总边权,最后再减
    #include <iostream>
    #include <algorithm>
    using namespace std;
    int n,k;
    struct edge{
    int u,v,w;
    //边集数组按边权从小到大排序
    friend bool operator <(edge a, edge b){
    return a.w<b.w;
    }
    }e[10000];//边集数组
    edge mst[10000];//存最小生成树的边
    int cnt;//最小生成树的边数
    int fa[110];
    int idx;//边集数组的有效边(非权值为0)

    //并查集查询+路径压缩
    int find(int x){
    if(fa[x]==x) return x;//如果是根结点就返回
    //否则就递归找根节点,并把沿途所有节点跟父节点更新为祖先节点
    return fa[x]=find(fa[x]);
    }
    long long sum;//最小生成树长度
    long long sum2;//重复边的边权(Σf(i,j)的最大值)

    //并查集链接
    void uni(int a,int b){
    int faa=find(a);//找a集合中的老大
    int fab=find(b);//找b集合中的老大
    //如果ab相同无事发生,不同就让a的老大成为b的老大的老大
    if(faa!=fab){
    fa[fab]=faa;
    }
    }
    void kruskal(){
    for(int i=1;i<=idx;i++){
    int a=e[i].u;//边集数组第i条边的一个端点
    int b=e[i].v;//边集数组第i条边的另外一个端点
    //如果a和b没有连通
    if(find(a)!=find(b)){//就把他们连上
    cnt++;
    mst[cnt].u=a;//然后存储边集数组
    mst[cnt].v=b;
    mst[cnt].w=e[i].w;
    sum+=e[i].w;//更新最小生成树长度
    uni(a,b);
    }
    else{//如果a和b已经连通
    sum2+=e[i].w;//e[i].w就是被除去网线的Σf(i,j)
    }
    }
    }

    int main(){
    cin>>n>>k;//n台计算机 k条边
    //初始化每个点自成集合
    for(int i=1;i<=n;i++) fa[i]=i;
    //存储边集数组
    for(int i=1;i<=k;i++){
    int u,v,w;
    cin>>u>>v>>w;
    if(w!=0){//w等于代表不存在这条边
    e[++idx].u=u;
    e[idx].v=v;
    e[idx].w=w;
    }
    }
    //对边集数组按边权从小到大排序 这样即使有重复边 也会连接上边权小的
    sort(e+1,e+idx+1);
    kruskal();
    cout<<sum2;
    return 0;
    }
    */


    3. 解法二:Prim 

    Prim的难点

  • 重边处理:邻接矩阵 g[u][v] 只能存一条边。在读入时,我们需要取 min(w,g[u][v]) 来保留最小边。那些被覆盖掉的较大边权,通过 tot (总权值) 统计,最后减去MST长度时自然就被“删除”了。

  • 非连通图(森林):标准的 Prim 从一个点(如1号点)开始,只能跑通一个连通块。如果局域网本身就是断开的几块,直接 prim(1) 会漏算其他部分。
    解决方案:在 main 函数中遍历所有点,如果 vis[i]==0,说明发现了一个新的连通块,对它执行 prim(i)。

  • 完整代码 

    //prim+邻接矩阵 复杂度:O(N^2)
    #include <iostream>
    #include <cstring>
    #include <algorithm>
    using namespace std;
    int n,k;
    int g[110][110];
    int vis[110];//标记该点是否已经加入集合
    long long tot;//一开始给出的所有边的总边权
    long long sum;//记录最小生成树的总长度
    int dis[110];//每个点到集合的距离

    void prim(int s){
    dis[s]=0;//起点到集合距离为0
    for(int i=1;i<=n;i++){//最多n个点加入集合
    int p=0;
    //每次在未加入集合的点中找到集合距离最短的
    for(int j=1;j<=n;j++){
    if(vis[j]==0 && dis[j]<dis[p])
    p=j;
    }
    //如果已经不存在可以加入最小生成树的点就退出
    if(p==0 || dis[p]==0x3f3f3f3f) break;
    vis[p]=1;//否则就把这个点加入集合
    sum+=dis[p];
    //然后用这个点去尝试更新所有它的未被点亮的邻接点到集合的距离
    for(int j=1;j<=n;j++){
    //如果邻接点未被点亮且原本到集合的距离大于经过p到集合的距离
    //就更新该距离
    if(vis[j]==0 && dis[j]>g[p][j]){
    dis[j]=g[p][j];
    }
    }
    }
    }
    int main(){
    cin>>n>>k;//n台计算机 k行
    memset(dis,0x3f,sizeof(dis));//初始化每个点到集合的距离为无穷
    memset(g,0x3f,sizeof(g));//初始化临界矩阵每个点之间距离为无穷
    //邻接矩阵
    for(int i=1;i<=k;i++){
    int u,v,w;
    cin>>u>>v>>w;
    tot+=w;
    if(w==0) continue;//w为0表示无网线连接
    //防止有重边,选择最短的
    g[u][v]=g[v][u]=min(w,g[u][v]);
    }
    //这道题不能直接prim(1),因为可能存在多个连通块,如果直接prim(1)
    //如果1-2 3-4 但是12与34之间未连通就是两个连通块,就会漏算
    //把34当成要删除的边,所以我们要遍历所有没加入集合的点 计算多个连通块的总sum
    //虽然这道题直接prim(1)也能过
    for(int i=1;i<=n;i++){
    if(vis[i]==0){
    prim(i);
    }
    }
    cout<<tot-sum;
    return 0;
    }

    4. 总结与对比

    特性 Kruskal算法 Prim算法
    核心数据结构 边集数组+并查集 邻接矩阵+距离数组
    处理重边 自动处理(排序后跳过) 需手动取 min
    处理森林 自动处理(天然支持) 需外层加循环遍历所有点
    答案计算 直接累加else分支的边 计算Total-MST
    适用场景 稀疏图、需要直接求“非树边”权值时 稠密图 (N<=1000)

    对于本题,Kruskal 的思维路径更短,代码更不易出错,是首选解法;但掌握 Prim 处理森林 的技巧(外层遍历 vis)也是非常重要的图论基本功。

    赞(0)
    未经允许不得转载:171主机测评 » 局域网(net)(信息学奥赛一本通- P1391)
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址