先介绍最小生成树的方法:
一、kruskal,思想是每次贪心地尝试将图中最小的非树边标记为树边,非法则跳过。也就是将全部的边的权值从小到大排序后,按顺序考虑每条边,只要这条边和已选的边不构成圈。就保留这条边,否则跳过。直到成功选取n-1条边。若无法选出n-1条边,说明原图不联通。
二、prim,基于点的贪心算法,核心思想是维护一个连通点集,每次都从不在该点集内的点中选出一个连通该点集的代价最小的点加入这个点集。
D - 数据中心
1.题目
Input
4
5
1
1 2 3
1 3 4
1 4 5
2 3 8
3 4 2
Output
4
2.解题思路
本道题需要解决的问题是求解出一颗生成树使得最大边权最小。这里采用kruskal解法。
当需要合并点的时候,用并查集进行合并。且用链式前向星存储图。
将所有边值按权值从小到大排序。遍历每条边并且合并这条边的起点和终点,直到选取到n-1条边,结果值为所有权值中最大的那个。注意这里用并查集合并点的时候需要使用到路径压缩。否则会超时。
3.c++代码
#include <stdio.h>
#include<stdlib.h>
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
const int N=5e4+5;
const int M=1e5+5;
int n,m,r;
struct edge
{
int u,v,w;
bool operator<(const edge &e)const
{return w<e.w;}//按权值从小到大排列
}edges[M];
int par[N];
void init(int n)
{
for(int i=1;i<=n;i++)par[i]=i;
}
int find(int x)
{
return x==par[x]?x:par[x]=find(par[x]);//路径压缩
}
bool unite(int x,int y)
{
x=find(x),y=find(y);
if(x==y)return false;
par[x]=y;
return true;
}
int kruskal()
{
sort(edges+1,edges+1+m);//注意编号从1开始
int tmp=n-1,ans=0;
for(int i=1;i<=m;i++)
{
if(unite(edges[i].u,edges[i].v))
{
tmp--;
ans=edges[i].w;
if(tmp==0)break;
}
}
return tmp==0?ans:-1;
}
int main()
{
cin>>n>>m>>r;
init(n);//记得写!!!
for(int i=1;i<=m;i++)
scanf("%d%d%d",&edges[i].u,&edges[i].v,&edges[i].w);
printf("%d\n",kruskal());
return 0;
}
C - 掌握魔法の东东 I
1.题目
东东在老家农村无聊,想种田。农田有 n 块,编号从 1~n。种田要灌氵
众所周知东东是一个魔法师,他可以消耗一定的 MP 在一块田上施展魔法,使得黄河之水天上来。他也可以消耗一定的 MP 在两块田的渠上建立传送门,使得这块田引用那块有水的田的水。 (1<=n<=3e2)
黄河之水天上来的消耗是 Wi,i 是农田编号 (1<=Wi<=1e5)
建立传送门的消耗是 Pij,i、j 是农田编号 (1<= Pij <=1e5, Pij = Pji, Pii =0)
东东为所有的田灌氵的最小消耗
input
第1行:一个数n
第2行到第n+1行:数wi
第n+2行到第2n+1行:矩阵即pij矩阵
output
东东最小消耗的MP值
example
Input
4
5
4
4
3
0 2 2 2
2 0 3 3
2 3 0 4
2 3 4 0
Output
9
2.解题思路
这题如果没有黄河之水,那么就是一个最小生成树问题。
在这里可以考虑将黄河之水天上来也作为一个节点,该节点与其他边之间的权值就是wi,对n+1个点进行最小生成树kruskal就好。
3.c++代码
#include <stdio.h>
#include<stdlib.h>
#include<iostream>
#include<algorithm>
using namespace std;
const int N=3e2+5;//最大点数
//const int M=1e5+5;
int n;
struct edge
{
int u,v,w;
bool operator<(const edge &e)const
{return w<e.w;}//按权值从小到大排列
}edges[50000];
int par[N];
void init(int n)
{
for(int i=0;i<=n;i++)par[i]=i;
}
int find(int x)
{
return x==par[x]?x:par[x]=find(par[x]);//路径压缩
}
bool unite(int x,int y)
{
x=find(x),y=find(y);
if(x==y)return false;
par[x]=y;
return true;
}
int kruskal(int tol)
{
sort(edges,edges+tol);//注意编号从0开始
int tmp=n,ans=0;
for(int i=0;i<tol;i++)
{
if(unite(edges[i].u,edges[i].v))
{
tmp--;
ans+=edges[i].w;
if(tmp==0)break;
}
}
return ans;
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
{
//节点编号从0开始,边编号从0开始
edges[i-1].u=0,edges[i-1].v=i;//黄河之水天上来
scanf("%d",&edges[i-1].w);
}
int tol=n,x;//后面加入的边的编号从n开始
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
{
scanf("%d",&x);
if(i>j)
{
edges[tol].u=i;
edges[tol].v=j;
edges[tol].w=x;
tol++;
}
}
//tot为边数
init(n);
printf("%d\n",kruskal(tol));
return 0;
}