题目描述
某个局域网内有 nn 台计算机和 kk 条 双向 网线,计算机的编号是 1∼n1∼n。由于搭建局域网时工作人员的疏忽,现在局域网内的连接形成了回路,我们知道如果局域网形成回路那么数据将不停的在回路内传输,造成网络卡的现象。
注意:
- 对于某一个连接,虽然它是双向的,但我们不将其当做回路。本题中所描述的回路至少要包含两条不同的连接。
- 两台计算机之间最多只会存在一条连接。
- 不存在一条连接,它所连接的两端是同一台计算机。
因为连接计算机的网线本身不同,所以有一些连线不是很畅通,我们用 $f(i,j)$ 表示 $i,j$ 之间连接的畅通程度,$f(i,j)$ 值越小表示 $i,j$ 之间连接越通畅。
现在我们需要解决回路问题,我们将除去一些连线,使得网络中没有回路且不影响连通性(即如果之前某两个点是连通的,去完之后也必须是连通的),并且被除去网线的 $Σf(i,j)$ 最大,请求出这个最大值。
输入格式
第一行两个正整数 $n,k$。
接下来的$ k$ 行每行三个正整数 $i,j,m$ 表示 $i,j$ 两台计算机之间有网线联通,通畅程度为$ m$。
输出格式
一个正整数,表示被除去网线的 $Σf(i,j)$ 的最大值。
数据范围
$1≤n≤100$
$0≤k≤200$
$1≤f(i,j)≤1000$
题目分析
这个题很容易看出来是最小生成树的题目,看了一会实在没有思路就看了$cf$大佬的题解分析如下
但是题中并没有说图一定联通,这让这道题恶心了很多。图不连通,就要先求出连通块,然后对每个连通块,求出其最小生成树。
看了$cf$大佬的分析,好像明白这个题是让求最小生成树森林,转念一想$kruskal$算法就是求连通块的,所以这个题直接用$kruskal$很容易求出来。
思考
不过数据范围那么小实在想用$prim$算法求一下,求最小生成树森林最直接的想法对每个点都求一遍最小生成树,当然如果已经在一个最小生成树的点就没必要再以该点求最小生成树(防止重复求),然后每次减去每个最小生成树的边,那么最终减去的就是最小生成树森林中的边,然后就是结果。
时间复杂度
对每一个点求最小生成树好像算法时间复杂度是$O(n^3)$不过其实是$O(n^2)$,每个点最终确定在一个最小生成树里面,每个点只会求一次$O(n)$,然后每次找不在集合的最小边$O(n)$,因此最终时间复杂度——$O(n^2)$
代码——prim
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=110,M=210;
int g[N][N];
int dist[N],m,n,res;
bool st[N];
void prim()
{
for(int k=1;k<=n;k++)
{
if(st[k]) continue;//已经在另一个最小生成树中
memset(dist,0x3f,sizeof dist);
dist[k]=0;
for(int i=1;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(t==-1) break;
st[t]=1;
for(int j=1;j<=n;j++)
if(!st[j]) dist[j]=min(dist[j],g[t][j]);
}
for(int i=1;i<=n;i++)
if(dist[i]!=0x3f3f3f3f) res-=dist[i];//每次减去最小生成树中的边
}
}
int main()
{
memset(g,0x3f,sizeof g);
cin>>n>>m;
while(m--)
{
int a,b,c;
cin>>a>>b>>c;
res+=c;//先把所有边加起来
g[a][b]=g[b][a]=min(g[a][b],c);
}
prim();
cout<<res<<endl;
}
%%%%%%%%%%%%%%%%%%%%%%