1.今天学习了Kruskal算法和Prim算法。
2.完成最小生成树的所有题目。
- [P3366 【模板】最小生成树](https://www.luogu.com.cn/problem/P3366)
题解:该题为模板题,我用的是Kruskal算法。
Kruskal算法核心思想:将所有边根据边权从小到大排序,每次从剩余的边中选择权值较小且边的两个顶点不在同一个集合的边(即不会产生回路的边),加入到生成树中,直到加入了n-1条边为止。
//kruskal
#include<bits/stdc++.h>
using namespace std;
struct Node
{
int x,y,z;
} e[200001];
bool compare(Node a,Node b)
{
return a.z<b.z;
}
int fa[5001],high[5001];
int findl(int x)
{
if(fa[x]==x)return x;
else
{
fa[x]=findl(fa[x]);
return fa[x];
}
}
int unionn(int x,int y)
{
int a=findl(x),b=findl(y);
if(a==b)return 0;//已连通
if(high[a]>high[b])
{
fa[b]=a;
return 1;//未连通
}
else
{
fa[a]=b;
if(high[a]==high[b])high[b]++;
return 1;
}
}
int main()
{
int n,m;
scanf("%d%d",&n,&m);
for(int i=1; i<=m; i++)
scanf("%d%d%d",&e[i].x,&e[i].y,&e[i].z);
sort(e+1,e+m+1,compare);
for(int i=1; i<=n; i++)
{
fa[i]=i;
high[i]=1;
}
int num=0,sum=0;
//kruskal核心
for(int i=1; i<=m; i++) //从小到大开始枚举每一条边
{
if(unionn(e[i].x,e[i].y))
{
num++;
sum+=e[i].z;
}
if(num==n-1)break;
}
int countt=0;
for(int i=1;i<=n;i++)
{
if(fa[i]==i)countt++;
if(countt>1)
{
printf("orz");
return 0;
}
}
printf("%d",sum);
return 0;
}
- [P2121 拆地毯](https://www.luogu.com.cn/problem/P2121)
题解:这题其实只要在上面一题的基础上略作修改即可,题目是求这 k条地毯的美丽度之和的最大值,且不能成环。那我们就以地毯的美丽值从大到小排序,以这k条边来连接各顶点,且不能成环。最后把这k条边的边权相加即可。
#include<bits/stdc++.h>
using namespace std;
struct Node
{
int x,y,z;
} e[100001];
bool compare(Node a,Node b)
{
return a.z>b.z;//从大到小排序
}
int fa[100001],high[100001];
int findl(int x)
{
if(fa[x]==x)return x;
else
{
fa[x]=findl(fa[x]);
return fa[x];
}
}
int unionn(int x,int y)
{
int a=findl(x),b=findl(y);
if(a==b)return 0;//已连通
if(high[a]>high[b])
{
fa[b]=a;
return 1;//未连通
}
else
{
fa[a]=b;
if(high[a]==high[b])high[b]++;
return 1;
}
}
int main()
{
int n,m,k;
scanf("%d%d%d",&n,&m,&k);
for(int i=1; i<=m; i++)
scanf("%d%d%d",&e[i].x,&e[i].y,&e[i].z);
sort(e+1,e+m+1,compare);
for(int i=1; i<=n; i++)
{
fa[i]=i;
high[i]=1;
}
int num=0,sum=0;
for(int i=1; i<=m; i++)
{
if(unionn(e[i].x,e[i].y))
{
num++;
sum+=e[i].z;
}
if(num==k)break;
}
printf("%d",sum);
return 0;
}
- [P1195 口袋的天空](https://www.luogu.com.cn/problem/P1195)
题解:
如果n个点被n-1条边连接的话,这一定是棵树。
那么:
连的边数 得到的树的个数
n-1 1
n-2 2
n-3 3
... ...
n-k k
所以我们如果想要连出k棵树,就需要连n-k条边。因为求的是最小代价,所有还要把边按代价从小到大排个序。如果给的关系数m小于需要连的边数(n-k),是一定连不出k个树来的,因为m个关系只能连m条边。
#include<bits/stdc++.h>
using namespace std;
struct Node
{
int x,y,z;
} e[10001];
bool compare(Node a,Node b)
{
return a.z<b.z;
}
int fa[1001],high[1001];
int findl(int x)
{
if(fa[x]==x)return x;
else
{
fa[x]=findl(fa[x]);
return fa[x];
}
}
int unionn(int x,int y)
{
int a=findl(x),b=findl(y);
if(a==b)return 0;//已连通
if(high[a]>high[b])
{
fa[b]=a;
return 1;//未连通
}
else
{
fa[a]=b;
if(high[a]==high[b])high[b]++;
return 1;
}
}
int main()
{
int n,m,k;
scanf("%d%d%d",&n,&m,&k);
for(int i=1; i<=m; i++)
scanf("%d%d%d",&e[i].x,&e[i].y,&e[i].z);
if(m<n-k)
{
printf("No Answer");
return 0;
}
sort(e+1,e+m+1,compare);
for(int i=1; i<=n; i++)
{
fa[i]=i;
high[i]=1;
}
int num=0,sum=0;
for(int i=1; i<=m; i++)
{
if(unionn(e[i].x,e[i].y))
{
num++;
sum+=e[i].z;
}
if(num==n-k)break;
}
printf("%d",sum);
return 0;
}
- [P1991 无线通讯网](https://www.luogu.com.cn/problem/P1991)
题解:通过这题我还了解到了一个新的概念,瓶颈生成树
最小生成树是瓶颈生成树的充分不必要条件,即最小生成树一定是瓶颈生成树,这题求的也就是最小生成树的最大边权。首先我们要求出各点间的距离,因为这是无向图,我们可以循环嵌套枚举求出各个点与其后点的距离。如果这题没有卫星电话的话,那么就是用p-1条边连出一颗最小生成树,取它的最大边权即可。那现在有了卫星电话,它的改变是什么呢。
假设有2个卫星电话,那么我就可以只用p-2条边连成一颗最小生成树,剩下的一个顶点我们可以用卫星电话的方式让它与最小生成树连接。
假设有1个卫星电话,那么他无法起连接作用,我们还是得用p-1条边连出一颗最小生成树,然后取它的最大边权
综上我们只需要用p-s条边连出一颗最小生成树,并取它的最大边权即可·。
#include<bits/stdc++.h>
using namespace std;
struct Node
{
int x,y;
double z;
} e[250001];
struct Pos
{
int x,y;
}idx[501];
bool compare(Node a,Node b)
{
return a.z<b.z;
}
int fa[501],high[501];
int findl(int x)
{
if(fa[x]==x)return x;
else
{
fa[x]=findl(fa[x]);
return fa[x];
}
}
int unionn(int x,int y)
{
int a=findl(x),b=findl(y);
if(a==b)return 0;//已连通
if(high[a]>high[b])
{
fa[b]=a;
return 1;//未连通
}
else
{
fa[a]=b;
if(high[a]==high[b])high[b]++;
return 1;
}
}
int main()
{
int s,p,num=0;
scanf("%d%d",&s,&p);
for(int i=1; i<=p; i++)
scanf("%d%d",&idx[i].x,&idx[i].y);
for(int i=1; i<=p; i++)
{
for(int j=i+1; j<=p; j++)
{
num++;
e[num].x=i;
e[num].y=j;
e[num].z=sqrt((double)((idx[i].x-idx[j].x)*(idx[i].x-idx[j].x)+(idx[i].y-idx[j].y)*(idx[i].y-idx[j].y)));//计算各点的距离
}
}
sort(e+1,e+num+1,compare);
for(int i=1; i<=p; i++)
{
fa[i]=i;
high[i]=1;
}
int counnt=0;
double ans=0;
for(int i=1; i<=num; i++)
{
if(unionn(e[i].x,e[i].y))
{
counnt++;
if(ans<e[i].z)ans=e[i].z;
}
if(counnt==p-s)
{
printf("%0.2lf",ans);
break;
}
}
return 0;
}
- [P2872 [USACO07DEC]Building Roads S](https://www.luogu.com.cn/problem/P2872)
题解:这题也是只给了点的坐标,我们需要像上题一样,循环嵌套枚举求出各个点与其后点的距离。这题有点特殊的是它会给你一些已知边,我的想法就是把这些已知边也存到数组里面去,只不过长度为0。这题有些坑的就是在计算各个点之间的距离时要先转换成double,因为题目说了点的坐标都为整数,这样能保证sqrt开出来时精度不会丢失。
#include<bits/stdc++.h>
using namespace std;
struct Node
{
int x,y;
double z;
} e[1000001];
struct Pos
{
int x,y;
} idx[1001];
bool compare(Node a,Node b)
{
return a.z<b.z;
}
int fa[1001],high[1001];
int findl(int x)
{
if(fa[x]==x)return x;
else
{
fa[x]=findl(fa[x]);
return fa[x];
}
}
int unionn(int x,int y)
{
int a=findl(x),b=findl(y);
if(a==b)return 0;//已连通
if(high[a]>high[b])
{
fa[b]=a;
return 1;//未连通
}
else
{
fa[a]=b;
if(high[a]==high[b])high[b]++;
return 1;
}
}
int main()
{
int n,m,num=0;
scanf("%d%d",&n,&m);
for(int i=1; i<=n; i++)
scanf("%d%d",&idx[i].x,&idx[i].y);
for(int i=1; i<=n; i++)
{
for(int j=i+1; j<=n; j++)
{
num++;
e[num].x=i;
e[num].y=j;
e[num].z=sqrt((double)(idx[i].x-idx[j].x)*(idx[i].x-idx[j].x)+(double)(idx[i].y-idx[j].y)*(idx[i].y-idx[j].y));
}
}
for(int i=1; i<=m; i++)
{
num++;
scanf("%d%d",&e[num].x,&e[num].y);
e[num].z=0;
}
sort(e+1,e+num+1,compare);
for(int i=1; i<=n; i++)
{
fa[i]=i;
high[i]=1;
}
int counnt=0;
double ans=0;
for(int i=1; i<=num; i++)
{
if(unionn(e[i].x,e[i].y))
{
counnt++;
ans+=e[i].z;
}
if(counnt==n-1)
{
printf("%0.2lf",ans);
break;
}
}
return 0;
}