局域网--

局域网


题目描述

局域网--


核心思路

题意:给定一张有 n n n个点, k k k条边的无向有环图,要求删掉其中部分边,且不改变图的连通性的情况下,使得最终的图仍然是连通的不存在环,并且删除的边权之和最大,输出最大的所删边边权权和。从题意我们可以发现:

删边权和最大    ⟹    \implies ⟹ 剩下边权和最小    ⟹    \implies ⟹ 求最小生成树

但是题中并没有说图一定连通,这让这道题恶心了很多。也就是说,图中可能存在多棵树,这些树并不在同一个连通块中。假设有 A 1 A_1 A1​, A 2 A_2 A2​, A 3 A_3 A3​这三颗树,它们都是独立的连通块,那么我们需要求出每树的最小生成树,不妨设为 x 1 , x 2 , x 3 x_1,x_2,x_3 x1​,x2​,x3​,设这三棵树边权总和为 s u m sum sum,那么最终删除的边权之和最大为 s u m − ( x 1 + x 2 + x 3 ) sum-(x_1+x_2+x_3) sum−(x1​+x2​+x3​)。

说到连通块,我们很容易想到Kruskal算法,它也是利用连通块思想来求解最小生成树。因此,这题我们可以采用Kruskal算法来求解,对于一棵树来说,如果两个点不在同一个集合中,则说明它是最小生成树中的边,那么就可以累加边权。如果两个点已经在一个集合中了,根据Kruskal算法思想,则不需要累加边权了。

当然这题也是可以用prim算法的。

下面给出一个有多棵树的栗子:

局域网--

如图所示:

将所有边权从小到大排好序,初始化每个节点都是独立集合(独立的连通块),设 a n s = 0 ans=0 ans=0

  • 对于树 A A A,节点 1 1 1和节点 2 2 2起初都是独立的集合,那么可以使用并查集进行合并,因此需要累加边权,即 a n s + = w ans+=w ans+=w,也就是 a n s = 0 + 10 = 10 ans=0+10=10 ans=0+10=10,此时树 A A A就已经是一个连通块了
  • 对于树 B B B,由于 ( 3 , 4 ) (3,4) (3,4)边权小,所有选择这条边,节点 3 , 4 3,4 3,4起初都是独立的集合,那么可以使用并查集进行合并,更新节点 4 4 4的祖宗节点为 3 3 3,因此需要累加边权,即 a n s + = w ans+=w ans+=w,也就是 a n s = 10 + 11 = 21 ans=10+11=21 ans=10+11=21,此时节点 3 3 3和节点 4 4 4就属于同一个连通块了。接着由于 ( 4 , 5 ) (4,5) (4,5)边权小,所以选择这条边,经过上一步的操作后,此时 ( 3 , 4 ) (3,4) (3,4)属于一个连通块,而 5 5 5是独立的一个集合,因此 5 5 5属于另一个连通块,那么可以使用并查集进行合并,更新节点 5 5 5的祖宗节点为 3 3 3,因此需要累加边权,即 a n s + = w ans+=w ans+=w,也就是 a n s = 21 + 12 = 33 ans=21+12=33 ans=21+12=33,此时 ( 3 , 4 , 5 ) (3,4,5) (3,4,5)已经在同一个连通块中了。最后选择 ( 3 , 5 ) (3,5) (3,5)这条边,但是由于 ( 3 , 4 , 5 ) (3,4,5) (3,4,5)已经在同一个连通块中了,所以此时 ( 3 , 5 ) (3,5) (3,5)这条边并不需要累加权值。也就是说 ( 3 , 5 ) (3,5) (3,5)这条边并不是最小生成树上的边。所以 a n s ans ans仍为 33 33 33,树 B B B的最小生成树的边有 ( 3 , 4 ) (3,4) (3,4)和 ( 4 , 5 ) (4,5) (4,5)
  • 对于树 C C C,由于 ( 7 , 6 ) (7,6) (7,6)边权小,所以选择这条边,节点 7 , 6 7,6 7,6起初都是独立的集合,那么可以使用并查集进行合并,更新节点 7 7 7的祖宗节点为 6 6 6,因此需要累加边权,即 a n s + = w ans+=w ans+=w,也就是 a n s = 33 + 14 = 47 ans=33+14=47 ans=33+14=47,那么此时节点 7 7 7和节点 6 6 6就已经在同一个连通块中了。接着考虑 ( 6 , 7 ) (6,7) (6,7)这条边,由于 ( 6 , 7 ) (6,7) (6,7)已经在同一个连通块中了,所以这条边并不需要累加权值。也就是说 ( 6 , 7 ) (6,7) (6,7)这条边并不是最小生成树上的边。所以 a n s ans ans仍为 47 47 47,树 C C C的最小生成树的边有 ( 7 , 6 ) (7,6) (7,6)
  • 由于这三颗树的边权总和为 s u m = 75 sum=75 sum=75,而我们已经求出了这三棵树的最小生成树之和为 47 47 47,那么需要删除的边的最大权值为 s u m − r e s = 75 − 47 = 28 sum-res=75-47=28 sum−res=75−47=28,也就是图中的红色边

代码

写法1:Kruskal算法

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=110,M=210,INF=0x3f3f3f3f;
int n,m;
struct Edge{
    int a,b,w;
    bool operator < (const Edge& W)const{
        return w<W.w;
    }
}edges[M];
int p[N];
int find(int x)
{
    if(x!=p[x])
        p[x]=find(p[x]);
    return p[x];
}
int Kruskal()
{
    sort(edges,edges+m);    //将边权从小到大排序
    for(int i=1;i<=n;i++)   //初始化每个点都是独立的集合
        p[i]=i;
    int res=0;      //最小生成树的权值
    //由于边已经排好序了  所以这里是优先选择小的边
    //要注意这些边并不都是连通的  (存在多个非连通块)
    for(int i=0;i<m;i++)
    {
        int a=find(edges[i].a);
        int b=find(edges[i].b);
        int w=edges[i].w;
        //对于每个连通块来说,我们都只会在当两个节点的集合号不同时 才累加权值
        //因为Kruskal算法就是当合并两个集合时,才累加了边权,而如果已经在同一个连通块中了,那么就不会累加
        if(a!=b)
        {
            p[a]=b;
            //只有不在同一集合时,才累加权值
            res+=w;
        }
    }
    //返回最小生成树的权值
    return res;    
}
int main()
{
    int sum=0;
    scanf("%d%d",&n,&m);
    for(int i=0;i<m;i++)
    {
        int a,b,w;
        scanf("%d%d%d",&a,&b,&w);
        edges[i]={a,b,w};
        //计算全部边权的总和
        sum+=w;
    }
    int t=Kruskal();
    //总和减去最小生成树,那么剩下的其实就是被除去网线的最大值
    printf("%d\n",sum-t);
    return 0;
}

写法2:

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=110,INF=0x3f3f3f3f;
//prim算法需要用到邻接矩阵
int g[N][N];
int n,k;
//dist[i]表示节点i到集合S的距离
int dist[N];
//st[i]=true表示节点i已经被加入了集合S中
bool st[N];
//prim算法求解最小生成树
int prim()
{
    int res=0;
    memset(dist,0x3f,sizeof dist);
    for(int i=0;i<n;i++)
    {
        int t=-1;
        for(int j=1;j<=n;j++)
            if(!st[j]&&(t==-1||dist[t]>dist[j]))
                t=j;
        if(i&&dist[t]==INF)
            continue;
        if(i)
            res+=dist[t];
        st[t]=true;
        for(int k=1;k<=n;k++)
            dist[k]=min(dist[k],g[t][k]);
    }
    return res;
}
int main()
{
    memset(g,0x3f,sizeof g);    //初始化邻接矩阵为正无穷
    scanf("%d%d",&n,&k);
    int total;  //记录输入的所有子树的权值总和
    while(k--)
    {
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        //防止重边和自环
        g[a][b]=g[b][a]=min(g[a][b],c);
        //算出整个森林的边权总和
        total+=c;
    }
    //由于题目给定的树不一定都是连通的,也就是说可能存在多个互不连通的树,即存在多个连通块
    //依次求解每颗树中的最小生成树
    for(int i=1;i<=n;i++)
    {
        //如果还没有处理节点i所在的树,则去求解i所在的这颗树的最小生成树
        if(!st[i])
            total-=prim();  //用总和减去每一颗树的最小生成树
    }
    printf("%d\n",total);
    return 0;
}

上一篇:halcon-数组


下一篇:剑指Offer(47)-- 1+2+...+n的求和(不使用循环或者乘法)