#最小生成树
无向图 G=(V,E) n个顶点,m条边
使用图中有的边,将所有顶点连起来(n个顶点,则需要n-1条边)这个子图称为生成树,而使用的边的权值之和最小的子图称为最小生成树。
step1.将所有的边按照权值排序(生成树中的两个端点或者两个连通分量之间,一定是通过存在且权值最小的边相连)
**step2** 所有的点作为单独的集合(一个点是一个集合)(并查集初始化) **step3** 将通过边连接各集合,最终的生成树即所有点都在一个集合中。选择加入新的边(已排序即最优的),如果该边连接的两个端点不再一个集合中(并查集判别),加入该条边,并将两个集合合并(并查集合并)。以邻接矩阵的形式给出图,求解最小生成树代价。kruskal和prime算法都可以。
#include
using namespace std;
#define Maxx 10010
int N,ans,cnt,val,x,y;
int p[Maxx];
struct Node{
int x,y,val;
}a[Maxx];
bool cmp(Node n1,Node n2){
return n1.val<n2.val;
}
void init(){//初始化
for(int i=1;i<Maxx;i++) p[i]=i;
}
int getP(int x){//获取父亲
if(p[x]!=x) return p[x]=getP(p[x]);//压缩路径压缩
return x;
}
int kruskal(){
sort(a+1,a+cnt+1,cmp);
init();
for(int i=1;i<=cnt;i++){//遍历边
x=a[i].x;
y=a[i].y;
int px=getP(x),py=getP(y);
if(px!=py){//边的两个顶点不再同一个集合则加入,并且合并两集合
p[py]=px;//合并操作
ans+=a[i].val;//累加生成树边权值
}
}
}
int main()
{
cin>>N;
for(int i=1;i<=N;i++){
for(int j=1;j<=N;j++){
cin>>val;
if(val!=0) a[++cnt]={i,j,val};
}
}
kruskal();
cout<<ans<<endl;
}
题目描述
在坐标系中的n 坐标(,x,y)
个点,给出m
条边连接(i,j)
,边的长度等于在坐标系中的距离。问添加最小的边的长度,使所有点连通。
题目思路
最小生成树具有连通且需花费权值最小的性质。对于已经加入的边(m条已有的边),花费就是0了,然后枚举任意两个点,可得到一个完全图,然后用kruskal
计算最小生成树的代价。
Code
#include
using namespace std;
#define N 500010
struct Node{
int x,y;
double val;
};
Node a[N],b[N];
int n,m,x,y,w,cnt;
double sum;
int p[5100];
bool cmp(Node n1,Node n2){
return n1.val<n2.val;
}
void init(){
for(int i=1;i<=n;i++) p[i]=i;
}
int findP(int x){
if(p[x]==x) return x;
return p[x]=findP(p[x]);
}
void kruskal(){
for(int i=1;i<=cnt;i++){
int px=findP(a[i].x);
int py=findP(a[i].y);
if(px!=py){
p[px]=py;
sum+=a[i].val;
}
}
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>b[i].x>>b[i].y;
}
init();
for(int i=1;i<=m;i++){
cin>>x>>y;
int px=findP(x),py=findP(y);
p[px]=py;
}
for(int i=1;i<=n;i++){
for(int j=i+1;j<=n;j++){
a[++cnt].x=i;
a[cnt].y=j;
double dx=1.0*(b[i].x-b[j].x);
double dy=1.0*(b[i].y-b[j].y);
a[cnt].val=sqrt(dx*dx+dy*dy);
}
}
sort(a+1,a+1+cnt,cmp);
kruskal();
printf("%.2f",sum);
}
prim
算法类似于dijkstra
算法,
step1.将1号结点加入生成树集合T,其余为生成树集合S,用数组D[i]
标记集合S任意点到集合T中i的最小距离,vis[x]
标记顶点x是否在生成树集合S中。(初始D[i]=G[1,i] vis[1]=1 vis[其余]=0
)
step2.每次从D[i]
中寻找最小值且未在集合S中的顶点i
,更新D[j]
i
与j
有边相连 (D[j]=min(D[i],G[i][j])
) ,直到所有顶点都加入生成树集合S中。
#include
using namespace std;
#define NUM 5100
int n,m,k,x,y,z,ans,cnt;
int vis[NUM],d[NUM];
//prim算法数据结构,vis数组(是否加入生成树集合) d数组距离(选择最优)
priority_queue< pair<int,int> >q;
//大根堆,使用负数变为小根堆,第一维为-d[i],第二位i
int head[NUM],next_[400005],ver[400005],edge[400005],tot;//数组模拟邻接表
//注意点!!! M≤2×10^5 因为是双向边存储所以边的数目必须是这个数据的二倍
void add(int x,int y ,int z){
ver[++tot]=y;
edge[tot]=z;
next_[tot]=head[x];
head[x]=tot;
}
void prim(){
//如果多组数据不要忘了初始化,memset可能会超时可以选择循环赋值
memset(d,0x3f,sizeof(d));
d[1]=0;
q.push(make_pair(0,1));
while(!q.empty()&&cnt<n){
x=q.top().second;z=-q.top().first;
q.pop();
if(vis[x]) continue;
cnt++;
ans+=d[x];
vis[x]=1;
for(int i=head[x];i;i=next_[i]){
y=ver[i],z=edge[i];
if(!vis[y]&&d[y]>z){
d[y]=z;
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();
if(cnt==n) cout<<ans;
else cout<<"orz";
}
题目描述
坐标系内有n个哨所,哨所之间通信需要通信设备,有两种通信设备可供使用,
1.两个哨所都安装了卫星通信,可以实现0距离花费通信。
2.安装有线设备,这种设备最大只能D距离通信。因为设备标准需要统一,所以所有有线设备的最大通信距离相同。
现在要使所有哨所都能够通过某种方式直接或者间接的通信,给出可以安装卫星通信的设备数量,数量不足需要安装有线设备,问有线设备的通信距离D是多少?
解题思路
使所有哨所连通,生成树?根据题目,考虑相关因素,卫星通信不占用且不需要任何有线的花费,所以s个卫星设备可将s个哨所无花费相连。剩余n-s
(设哨所集合A
) 与s
(设哨所集合B) ,需要通过有线设备连通A
,同时需要线路连通A
与B
。计算出花费最大值,就是答案了。
#include
using namespace std;
#define N 510
int s,n,v[N],x,y,w;
double d[N],g[N][N],cost[N];//cost加入点i的花销
pair<int,int>a[N];//保存个哨所坐标
priority_queue<pair<int,int> >heap;
void prim(){//只需加入n-s+1个点,不如将加入的n-1个点的权值放在数组中,排序取后n-s个小的
for(int i=1;i<=n;i++) d[i]=100100;//不要忘记初始化哦
d[1]=0;
heap.push(make_pair(0,1));
while(!heap.empty()){
x=heap.top().second;
heap.pop();
if(v[x]) continue;
v[x]=1;
cost[x]=d[x];
//cout<
for(int i=1;i<=n;i++){
//printf("d[%d]=%f g[%d][%d]=%f\n",i,d[i],x,i,g[x][i]);
if(!v[i]&&d[i]>g[x][i]){
d[i]=g[x][i];
heap.push(make_pair(-d[i],i));
}
}
}
//for(int i=1;i<=n;i++) cout<
//cout<
sort(cost+1,cost+1+n);//n-(s-1)=n-s+1
printf("%.2f",cost[n-s+1]);
}
int main()
{
cin>>s>>n;
for(int i=1;i<=n;i++) cin>>a[i].first>>a[i].second;
if(s>=n){
cout<<0<<endl;
return 0;
}
for(int i=1;i<=n;i++) g[i][i]=0;
for(int i=1;i<=n;i++){
for(int j=i+1;j<=n;j++){
double dx=a[i].first-a[j].first;
double dy=a[i].second-a[j].second;
g[j][i]=g[i][j]=sqrt(dx*dx+dy*dy);
}
}
prim();
}
已经结束了,只能走到这了(时间:2022/3/22 5:25 确实*25达不到)。或许能力有限,或许努力不足。
计划的还没做完,短期内没有任何意义了。
仿佛陷入循环,咫尺之距,遥不可及。
给自己一个缓冲,4.15后,之后再见,会回来的。