【洛谷P2820】局域网
今天心情很好 加训加训
学习了最小生成树的Kruscal(不知道有没有拼错)算法
我们来看一下模板:
P3366 【模板】最小生成树
题目描述
如题,给出一个无向图,求出最小生成树,如果该图不连通,则输出 orz。
输入格式
第一行包含两个整数 \(N,M\),表示该图共有 \(N\) 个结点和 \(M\) 条无向边。
接下来 \(M\) 行每行包含三个整数 \(X_i,Y_i,Z_i\),表示有一条长度为 \(Z_i\) 的无向边连接结点 \(X_i,Y_i\)。
输出格式
如果该图连通,则输出一个整数表示最小生成树的各边的长度之和。如果该图不连通则输出 orz。
输入输出样例 #1
输入 #1
4 5
1 2 2
1 3 2
1 4 3
2 3 4
3 4 3
输出 #1
7
说明/提示
数据规模:
对于 \(20\%\) 的数据,\(N\le 5\),\(M\le 20\)。
对于 \(40\%\) 的数据,\(N\le 50\),\(M\le 2500\)。
对于 \(70\%\) 的数据,\(N\le 500\),\(M\le 10^4\)。
对于 \(100\%\) 的数据:\(1\le N\le 5000\),\(1\le M\le 2\times 10^5\),\(1\le Z_i \le 10^4\)。
样例解释:

所以最小生成树的总边权为 \(2+2+3=7\)。
解法&&个人感想
生成树指的是对于一个无向图,给定对于这个无向图中的n个点,需要连接(n-1)条边,保证这个图的连通性
而最小生成树指的是 对于这(n-1)条边的总边权最小的一棵生成树
我们这里使用Kruscal算法(后面可能会补上Prim)
这是一个偏向于贪心的算法 使用并查集维护
首先,我们初始化并查集(废话)
然后,输入每条边的起点终点和边权,这里不需要链式前向星/邻接表/邻接矩阵存图
以边权为关键字(py学多了)将这n条边排序
从最小的开始 如果两个点之间直接或间接联通 就跳过
否则 总和加上边权 合并两个点所在的森林
Kruscal算法的时间复杂度为O(mlogm) 因此多用于稀疏图中
下面直接上代码:
#include<bits/stdc++.h>
#define ll long long
#define ull unsigned long long
#define maxn 10005
#define maxm 400005
using namespace std;
struct Edge{
int from,to,w;
};
Edge edge[maxm];
int fa[maxn];
int n,m;
ll ans=0;
int cnt=0;
bool cmp(Edge x,Edge y){
return x.w<y.w;
}
int get(int x){
if(fa[x]==x) return x;
return fa[x]=get(fa[x]);
}
void merge(int x,int y){
fa[x]=y;
return ;
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
scanf("%d%d%d",&edge[i].from,&edge[i].to,&edge[i].w);
}
sort(edge+1,edge+1+m,cmp);
for(int i=1;i<=n;i++) fa[i]=i;
for(int i=1;i<=m;i++){
int x=get(edge[i].from),y=get(edge[i].to);
if(x==y) continue;
ans+=edge[i].w;
merge(x,y);
cnt++;
}
if(cnt==n-1) printf("%lld",ans);
else printf("orz\n");
system("pause");
return 0;
}
然后 我们来讲一下prim算法
这个算法跟Kruscal有点像 但不大一样
它总是维护最小生成树的一部分
一开始,只有1号节点属于生成树
每次找出分别属于S,T(T代表已经选入最小生成树)的权值最小的一条边 并将S中的该节点加入T
维护一个数组d d[i]当x∈S时表示x与T中节点之间权值最小的边的权值 而当x∈T时等于x被加入T时选出的最小边的权值
prim算法的时间复杂度为O($ n^2 $) 因此多用于稠密图中
下面看代码:
#include<bits/stdc++.h>
#define ll long long
#define ull unsigned long long
#define maxn 5005
#define maxm 400005
#define INF 2147483647
using namespace std;
struct Edge{
int to,nex,w;
};
Edge edge[maxm];
int head[maxm];
int n,m,tot;
int x,y,z;
int vis[maxn];
int d[maxn];
void add(int x,int y,int z){
edge[++tot].to=y;
edge[tot].w=z;
edge[tot].nex=head[x];
head[x]=tot;
}
ll prim(){
ll ans=0;
for(int i=0;i<=n;i++) d[i]=INF;
memset(vis,0,sizeof(vis));
d[1]=0;
for(int i=1;i<=n;i++){
int x=0;
for(int j=1;j<=n;j++){
if(!vis[j]&&d[j]<d[x]) x=j;
}
vis[x]=1;
for(int j=head[x];j;j=edge[j].nex){
int y=edge[j].to;
if(!vis[y]) d[y]=min(d[y],edge[j].w);
}
}
for(int i=1;i<=n;i++){
if(d[i]==INF){
return -1;
}
ans+=d[i];
}
return ans;
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
scanf("%d%d%d",&x,&y,&z);
add(x,y,z);
add(y,x,z);
}
ll ans=prim();
if(ans==-1) printf("orz\n");
else printf("%lld\n",ans);
system("pause");
return 0;
}
由于prim算法的特性 我们还可以将其用堆优化 这样神似Dijkstra
时间复杂度为O(mlogn)
下面直接放代码:
#include<bits/stdc++.h>
#define ll long long
#define ull unsigned long long
#define maxn 5005
#define maxm 400005
#define INF 2147483547
using namespace std;
struct Edge{
int to,nex,w;
};
Edge edge[maxm];
int d[maxn];
int vis[maxn];
int head[maxm];
int n,m,tot;
int x,y,z;
priority_queue<pair<int,int>>q;
void add(int x,int y,int z){
edge[++tot].to=y;
edge[tot].w=z;
edge[tot].nex=head[x];
head[x]=tot;
}
void prim(){
memset(vis,0,sizeof(vis));
for(int i=0;i<=n;i++) d[i]=INF;
d[1]=0;
q.push(make_pair(0,1));
while(!q.empty()){
int x=q.top().second;q.pop();
if(vis[x]) continue;
vis[x]=1;
for(int i=head[x];i;i=edge[i].nex){
int y=edge[i].to;
if(!vis[y]&&d[y]>edge[i].w){
d[y]=edge[i].w;
q.push(make_pair(-d[y],y));
}
}
}
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
scanf("%d%d%d",&x,&y,&z);
add(x,y,z);
add(y,x,z);
}
prim();
ll flag=0;
for(int i=1;i<=n;i++){
if(d[i]==INF){
flag=-1;
break;
}
flag+=d[i];
}
if(flag==-1) printf("orz\n");
else printf("%lld\n",flag);
system("pause");
return 0;
}
想巩固prim算法的可以做做下面这道题:
洛谷P1547 Out of Hay S
下面 我们来看今天这道题
P2820 局域网
题目背景
某个局域网内有 \(n\) 台计算机,由于搭建局域网时工作人员的疏忽,现在局域网内的连接形成了回路,我们知道如果局域网形成回路那么数据将不停的在回路内传输,造成网络卡的现象。因为连接计算机的网线本身不同,所以有一些连线不是很畅通,我们用 \(f(i,j)\) 表示 \(i,j\) 之间连接的畅通程度,\(f(i,j)\) 值越小表示 \(i,j\) 之间连接越通畅,\(f(i,j)\) 为 \(0\) 表示 \(i,j\) 之间无网线连接。
题目描述
现在需要解决回路问题,我们将除去一些连线,使得网络中没有回路,不改变原图节点的连通性,并且被除去网线的 \(\sum f(i,j)\) 最大,请求出这个最大值。
输入格式
第一行两个正整数 \(n,k\)。
接下来的 \(k\) 行每行三个正整数 \(i,j,m\) 表示 \(i,j\) 两台计算机之间有网线联通,通畅程度为 \(m\)。
输出格式
一个正整数, \(\sum f(i,j)\) 的最大值。
输入输出样例 #1
输入 #1
5 5
1 2 8
1 3 1
1 5 3
2 4 5
3 4 2
输出 #1
8
说明/提示
对于全部数据,保证 \(1\le n \le 100\),\(1\le f(i,j)\le 1000\)。
解法&&个人感想
对于这道题 我就说一点
它让我们除去的边的总权值最大 同时又不影响原来的连通性
那么 就是需要找出最小生成树
然后把原本的总权值减去最小生成树的权值
这样就完成了
#include<bits/stdc++.h>
#define ll long long
#define ull unsigned long long
#define maxn 205
#define maxm 10005
using namespace std;
struct Edge{
int from,to,w;
};
Edge edge[maxm];
int fa[maxn];
int n,k;
ll ans=0;
ll total=0;
bool cmp(Edge x,Edge y){
return x.w<y.w;
}
int get(int x){
if(fa[x]==x) return x;
return fa[x]=get(fa[x]);
}
void merge(int x,int y){
fa[x]=y;
}
int main(){
scanf("%d%d",&n,&k);
for(int i=1;i<=k;i++){
scanf("%d%d%d",&edge[i].from,&edge[i].to,&edge[i].w);
total+=edge[i].w;
}
for(int i=1;i<=n;i++) fa[i]=i;
sort(edge+1,edge+1+k,cmp);
for(int i=1;i<=k;i++){
int x=get(edge[i].from),y=get(edge[i].to);
if(x==y) continue;
ans+=edge[i].w;
merge(x,y);
}
printf("%lld\n",total-ans);
system("pause");
return 0;
}
下面再放张图~

20250423 更新prim算法
再放一张图~


浙公网安备 33010602011771号