题目列表
最小生成树
Prim板子
先给一个我用的板子
- Prim算法 图的点集合为V 是从点出发 任意定一个初始节点加入集合X,寻找它所连接的最短边,然后将它连接的节点加入X中,用新连接的点更新d数组,直至X=V
- d数组存的是当前X集合对各个点的最短距离 vis记录这个点是否已经在X集合中
- 类似Dijkstra–每次找到最小的边
//无向图 N点 M条边 求最小生成树
//可能存在重边
#include<iostream>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<cstdio>
using namespace std;
const int N = 5100;
int g[N][N];//图的邻接矩阵
bool vis[N];//标记各个点是否已经进入集合X
int d[N];//X集合 到各个点的最短距离
int n,m;
int prim()
{
int sum=0;
memset(vis,0,sizeof(vis));
memset(d,0x3f,sizeof(d));
d[1]=0;
while(1)
{
int t=-1;
for(int i=1;i<=n;i++)
if(!vis[i]&&(t==-1||d[i]<d[t]))t=i;
if(t==-1)break;
if(d[t]==0x3f3f3f3f)return -1;
vis[t]=true;
sum+=d[t];
//cout<<t<<"+++"<<d[t]<<"+++"<<sum<<endl;
for(int i=1;i<=n;i++)
d[i]=min(d[i],g[t][i]);
}
for(int i=1;i<=n;i++)
if(!vis[i])return -1;
return sum;
}
int main()
{
scanf("%d%d",&n,&m);
memset(g,0x3f,sizeof(g));
while(m--)
{
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
g[a][b]=min(g[a][b],c);
g[b][a]=min(g[b][a],c);
}
int t=prim();
if(t==-1||n==1)printf("orz\n");
else printf("%d\n",t);
return 0;
}
Prim的例题
P2872 [USACO07DEC]Building Roads S
题目链接:P2872 [USACO07DEC]Building Roads S
题意:n个点 m条边
求加入一些边构成最小生成树
分析:Prim板子题 稍微改一下板子,既然已经有相连,那就把距离改成0就可以了,初始点设任意一个点都可以
知识点:Prim
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<string>
#include<cmath>
using namespace std;
const int N = 1100;
const double inf = 0x3f;
double g[N][N],d[N];
bool vis[N];
int n,m;
long long x[N],y[N];
double ju(int i,int j)
{
double xx=x[i]-x[j];
double yy=y[i]-y[j];
//cout<<xx<<' '<<yy<<endl;
return sqrt(xx*xx+yy*yy);
}
double prim()
{
double sum=0;
d[1]=0;
while(true)
{
int t=-1;
for(int i=1;i<=n;i++)
if(!vis[i]&&(t==-1||d[t]>d[i]))t=i;
if(t==-1)break;
sum+=d[t];
vis[t]=true;
//cout<<'+'<<sum<<endl;
for(int i=1;i<=n;i++)
d[i]=min(d[i],g[t][i]);
}
return sum;
}
int main()
{
scanf("%d%d",&n,&m);
memset(g,0x7f,sizeof(g));
//cout<<g[1][1]<<endl;
for(int i=1;i<=n;i++)
scanf("%lld%lld",&x[i],&y[i]);
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
g[i][j]=min(g[i][j],ju(i,j));//cout<<g[i][j]<<' ';
//cout<<endl;;
}
memset(d,0x7f,sizeof(d));
while(m--)
{
int a,b;
scanf("%d%d",&a,&b);
g[a][b]=0;
g[b][a]=0;
}
printf("%.2f\n",prim());
return 0;
}
P1194 买礼物
题目链接:P1194 买礼物
题意:买B件物品,每两个物品一起买有优惠,一个物品单独买价格为A,翻译一下,就是说每两个物品有一条边,权值为优惠价格,物品有一个自环是A,找最小生成树,画最少的钱买到所有物品
分析:建好图,跑一遍Prim
ps:初始点是A 第一个物品没有优惠 原价购买
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
using namespace std;
const int N = 510;
int g[N][N];
bool vis[N];
int d[N];
int A,B;
int prim()
{
memset(d,0x3f,sizeof(d));
d[1]=A;
int sum=0;
while(1)
{
int t=-1;
for(int i=1;i<=B;i++)
if(!vis[i]&&(t==-1||d[t]>d[i]))t=i;
if(t==-1)break;
vis[t]=true;
sum+=d[t];
//cout<<sum<<endl;
for(int i=1;i<=B;i++)
d[i]=min(d[i],min(g[t][i],g[i][i]));//存在自环
}
return sum;
}
int main()
{
scanf("%d%d",&A,&B);
for(int i=1;i<=B;i++)
{
for(int j=1;j<=B;j++)
{
int x;
scanf("%d",&x);
if(x==0)g[i][j]=A;
else g[i][j]=x;
//cout<<g[i][j]<<" ";
}
// cout<<endl;
}
cout<<prim()<<endl;
return 0;
}
/*
3 3
0 4 4
4 0 4
4 4 0
9
*/
P1265 公路修建
题目链接:P1265 公路修建
题意:跟上面P2872类似,这次没有相连的点,所有点开始都是一个城市,将他们联通到一个连通体,给各个城市的坐标,计算最小距离
分析:就是一个最小生成树的题,计算联通各个点的最小距离(不存各个点到其它点的距离了,直接到更新计算)
~~ps:Kruskal跑了90分 边太多了 5000*5000 MLE了 稠密图还是用Prim吧
~~
知识点:最小生成树 Prim
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
const int N = 5001;
double d[N];
bool vis[N];
int x[N],y[N];
int n;
double Dis(int i,int j)
{
double xx=x[i]-x[j];
double yy=y[i]-y[j];
return sqrt(xx*xx+yy*yy);
}
double prim()
{
memset(d,0x7f,sizeof(d));
d[1]=0;
double sum=0;
while(1)
{
int t=-1;
for(int i=1;i<=n;i++)
if(!vis[i]&&(t==-1||d[t]>d[i]))t=i;
if(t==-1)break;
sum+=d[t];
vis[t]=true;
for(int i=1;i<=n;i++)
d[i]=min(d[i],Dis(i,t));
}
return sum;
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
cin>>x[i]>>y[i];
printf("%.2f\n",prim());
return 0;
}
P1661 扩散
题目链接:P1661 扩散
题意:n个点,每个点可以在一个单位时间向四周扩散一个单位,问将所有点相连到一起的连通体需要经过的最短时间
分析:用最小生成树来做,那最短时间就是这些边里最长的,每两个点连起来,最后构成时间最小的生成树,不重复不成环
看题解好像也可以使用二分来做,大家可以去试试
知识点:Prim
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
const int N = 100;
bool vis[N];
int d[N];
int n;
int x[N],y[N];
int Dis(int i,int j)
{
int xx=fabs(x[i]-x[j]);
int yy=fabs(y[i]-y[j]);
if((xx+yy)%2==0)return (xx+yy)/2;
return (xx+yy)/2+1;
}
int Prim()
{
memset(d,0x3f,sizeof(d));
d[1]=0;
int ans=0;
while(1)
{
int t=-1;
for(int i=1;i<=n;i++)
if(!vis[i]&&(t==-1||d[t]>d[i]))t=i;
if(t==-1)break;
vis[t]=true;
ans=max(ans,d[t]);
//cout<<d[t]<<endl;
//cout<<d[t]<<endl;
for(int i=1;i<=n;i++)
d[i]=min(d[i],Dis(i,t));
}
return ans;
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
cin>>x[i]>>y[i];
cout<<Prim()<<endl;
return 0;
}
/*
5
46 16
6 5
33 18
5 17
7 25
*/
Kruskal
Kruskal板子
- 将所有边按权重从小到大排序
- 将最小权重的的边,取出来 a-b 权值为c
if(a 和 b 不连通)把这条边加入集合里 - 直到集合里的点==n
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N = 1e5+10,M = 2e5+10;
struct Node
{
int a,b,c;
}node[M];//存储边
int p[N];//并查集 各个节点的树根节点
int n,m;//点数 边数
bool cmp(const struct Node& x,const struct Node& y)
{
return x.c<y.c;
}
int find(int x)//查找x的树根节点
{
if(p[x]!=x)p[x]=find(p[x]);
return p[x];
}
int kruskal()//最小生成树
{
sort(node+1,node+1+m,cmp);//将边从小到大排序
for(int i=1;i<=n;i++)//初始化并查集 父亲开始均为自己
p[i]=i;
int res=0,cnt=0;//res-目前的代价 cnt-走过的边数
for(int i=1;i<=m;i++)
{
Node e=node[i];
if(find(e.a)==find(e.b))continue;//已经加入到集合里了 那就不需要再加这条边
p[find(e.a)]=find(e.b);
res+=e.c;
cnt++;
}
if(cnt<n-1)return -1;//边数<n-1 图不连通
return res;
}
int main()
{
cin>>n>>m;
for(int i=1;i<=m;i++)
{
scanf("%d%d%d",&node[i].a,&node[i].b,&node[i].c);
}
int t=kruskal();
if(t==-1)puts("impossible");
else printf("%d\n",t);
return 0;
}
Kruskal 例题
P1991 无线通讯网
题意:n个点s个卫星电话,将n个点通信,有直接和间接通信方式,直接相连,和两点都安装卫星电话也可以通信
分析:完全图,n个点,将各个点想连,按照距离排序,想让无线电收发器的传输距离最小,按照Kruskal算法,只需要将结束条件改为p-s后面距离大的带你都按转卫星电话,使传输距离最小
知识点:最小生成树
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
const int N = 500001;
struct Node
{
int a,b;
double c;
bool operator<(const Node & X)
{
return c<X.c;
}
}w[N];
int s,p,cnt=0;
int x[510],y[510];
int f[510];
double Dis(int i,int j)
{
double xx=x[i]-x[j];
double yy=y[i]-y[j];
return sqrt(xx*xx+yy*yy);
}
int findd(int x)
{
if(f[x]!=x)f[x]=findd(f[x]);
return f[x];
}
double kurskal()
{
sort(w,w+cnt);
for(int i=1;i<=p;i++)f[i]=i;
double mi;
int shu=0;
for(int i=0;i<cnt;i++)
{
Node e=w[i];
//cout<<e.c<<endl;
if(findd(e.a)==findd(e.b))continue;
f[findd(e.a)]=findd(e.b);
mi=max(e.c,mi);
shu++;
if(shu>=p-s)
{
return mi;
}
}
}
int main()
{
cin>>s>>p;
for(int i=1;i<=p;i++)
cin>>x[i]>>y[i];
for(int i=1;i<=p;i++)
for(int j=i+1;j<=p;j++)
w[cnt++]={i,j,Dis(i,j)};
double t=kurskal();
printf("%.2f\n",t);
return 0;
}
P2121 拆地毯
题意:有n个块区域,m个地毯将他们相连,最后需要k条地毯的最大美丽度
这k条,不能构成环
分析:最小生成树变形,求最大,将权值从小到大排序
知识点:并查集+Kruskal
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 1e5+10;
struct Node
{
int a,b,c;
bool operator < (const Node& X)
{
return c>X.c;
}
}w[N];
int n,m,k;
int p[N];
int fin(int x)
{
if(p[x]!=x)p[x]=fin(p[x]);
return p[x];
}
int Kruskal()
{
for(int i=1;i<=n;i++)p[i]=i;
sort(w,w+m);
int sum=0,shu=0;
for(int i=0;i<m;i++)
{
Node e=w[i];
if(fin(e.a)==fin(e.b))continue;
p[fin(e.a)]=fin(e.b);
shu++;
sum+=e.c;
if(shu>=k)return sum;
}
}
int main()
{
cin>>n>>m>>k;
for(int i=0;i<m;i++)
cin>>w[i].a>>w[i].b>>w[i].c;
int t=Kruskal();
printf("%d\n",t);
return 0;
}
P4047 [JSOI2010]部落划分
题意:n个野人居住的地点,要划分为k个部落,使靠近的两个点距离最大化,输出剩下最大距离里的最小距离
分析:就是说在一个完全图里,分为k个连通体,让每个连通图相互距离最大,其实就是一个最小生成树,将n-k的节点距离相对小的划分在一起,对其它节点来说就是最大,使用Kruskal算法将终止条件改为达到n-k就停止,然后输出下一个该划入集合的距离就是答案
知识点:并查集+Kruskal
#include<iostream>
#include<cstdio>
#include<string>
#include<algorithm>
#include<cmath>
using namespace std;
const int N = 1e6+10;
struct Node
{
int a,b;
double c;
bool operator < (const Node& X)
{
return c<X.c;
}
}w[N];
int x[N],y[N];
int p[N];
int n,m,cnt=0;
double Dis(int i,int j)
{
double xx=x[i]-x[j];
double yy=y[i]-y[j];
return sqrt(xx*xx+yy*yy);
}
int fin(int x)
{
if(p[x]!=x)p[x]=fin(p[x]);
return p[x];
}
void Kruskal()
{
sort(w,w+cnt);
for(int i=1;i<=n;i++)p[i]=i;
int shu=0;
for(int i=0;i<cnt;i++)
{
Node e=w[i];
if(fin(e.a)==fin(e.b))continue;
p[fin(e.a)]=fin(e.b);
shu++;
if(shu>n-m)
{
printf("%.2f\n",e.c);
break;
}
}
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
cin>>x[i]>>y[i];
for(int i=1;i<=n;i++)
for(int j=i+1;j<=n;j++)
w[cnt++]={i,j,Dis(i,j)};
Kruskal();
return 0;
}
P1195 口袋的天空
题目链接:P1195 口袋的天空
题意:大概就是说,现在窗外有n朵云,有m条边,你要将他们连成k个连通体,求最小代价
分析:板子,最小生成树,也就是说将连成k个连通体,肯定把短的连,这样才能代价最小,所以就连前面n-k的边就可以
看代码,Kruskal 当加入n-k边之后停止 若达不到n-k则构不成k个连通体
知识点:Kruskal+并查集
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstdio>
#include<cstring>
using namespace std;
const int N = 1e4+10;
struct Node
{
int a,b,c;
bool operator < (const Node & X)
{
return c<X.c;
}
}w[N];
int n,m,k;
int p[1001];
int fin(int x)
{
if(p[x]!=x)p[x]=fin(p[x]);
return p[x];
}
int Kruskal()
{
for(int i=1;i<=n;i++)p[i]=i;
int cnt=0,sum=0;
sort(w+1,w+1+m);
for(int i=1;i<=m;i++)
{
Node e=w[i];
if(fin(e.a)==fin(e.b))continue;
p[fin(e.a)]=fin(e.b);
cnt++;
sum+=e.c;
if(cnt>=n-k)
{
return sum;
}
}
return -1;
}
int main()
{
cin>>n>>m>>k;
for(int i=1;i<=m;i++)
cin>>w[i].a>>w[i].b>>w[i].c;
int t=Kruskal();
if(t==-1)puts("No Answer");
else printf("%d\n",t);
return 0;
}