题目https://www.luogu.org/problemnew/show/P3366
题解
Prim在稠密图中比Kruskal优,在稀疏图中比Kruskal劣。Prim是以更新过的节点的连边找最小值,Kruskal是直接将边排序。Prim以点找边,Kruskal找边避环。
模板参考算法笔记,尽量用邻接表来做,邻接矩阵可能会被卡。Kruskal和Prim(稠密图会MLE的话用这个),所以一般都用Kruskal
kruskal主要思路:
1.输入边,用结构体储存
2.用结构体快排以边比较从小到大快排(也可以用堆)
3.建一个并查集,并初始化并查集(并查集代表两个点有没有在同一个树里面)
Prim:
1.确定一个根节点
2.该节点所有的边依次进入优先队列
3.入队出队,知道队列为空
推荐方法一、方法二作为模板
方法一:Kruskal+堆优化(推荐模板)
#include<bits/stdc++.h>
using namespace std;
const int maxm=4000005;
const int maxn=5005;
struct Edge{
int u,v,w;
bool operator<(const Edge a) const{
return w>a.w;//优先队列设置为小根堆,优先级高的数字小
}
}edge[maxm];
priority_queue<Edge>q;//优先队列 ,堆优化
int n,m;
int fa[maxn];
inline int read()//读入优化
{
int x=0,w=0;char ch=0;
while(!isdigit(ch)) {w|=ch=='-';ch=getchar();}
while(isdigit(ch)){x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}
return w?-x:x;
}
inline int gf(int x){return fa[x]==x?fa[x]:fa[x]=gf(fa[x]);}//并查集
int main()
{
n=read(),m=read();
for(int i=1;i<=n;i++) fa[i]=i;//并查集初始化
for(int i=1;i<=m;i++) q.push((Edge){read(),read(),read()});//真骚
int ans=0,cnt=0;
while(!q.empty()){//挨个边来
Edge kkk=q.top();q.pop();
int ance1=gf(kkk.u),ance2=gf(kkk.v);//ancestor祖先
if(ance1!=ance2) ans+=kkk.w,cnt++,fa[ance1]=ance2;
if(cnt==n-1) break; //最小生成树最多有n-1条边
}
if(cnt<n-1) cout<<"orz"<<endl;
cout<<ans<<endl;
return 0;
}
方法二:堆优化+Prim+邻接表(前向星实现,推荐模板)
Prim要注意无向图的情况,要在前向星中实现两个方向的边的存储
#include<bits/stdc++.h>
#define R register int
using namespace std;
const int INF=2147483647;
int k,n,m,cnt,sum,ai,bi,ci,head[5005],vis[5005]={0};
int dis[5005];
//cnt是最小生成树中已经包含的点的个数
//dis[]存的是到某点的一条边的已知最小权值
struct Edge{
int v,w,next;
}e[400005];
void add(int u,int v,int w)
{//前向星
e[++k].v=v;
e[k].w=w;
e[k].next=head[u];
head[u]=k;
}//其他程序中,k通常写为cnt,是边的编号
typedef pair<int,int>pii;//定义别名,也可以用结构体代替pair
priority_queue<pii,vector<pii>,greater<pii> >q;//固定格式
//设置为小根堆 ,排序的关键字是pii.first
void prim()
{
dis[1]=0;//设置根节点为1
q.push(make_pair(0,1));//重要初始化
while(!q.empty()&&cnt<n){//cnt代表最小生成树中点的个数
int d=q.top().first,u=q.top().second;
//弹出权值最小的边
//d是权值,u是边上另一个点
q.pop();
if(vis[u]) continue;//u已经在最小生成树中
cnt++;sum+=d;vis[u]=1;
for(R i=head[u];i!=-1;i=e[i].next) //以点找边的过程
{//i!=-1与主程序中对head[]数组的赋值有关
if(e[i].w<dis[e[i].v])
dis[e[i].v]=e[i].w;
q.push(make_pair(dis[e[i].v],e[i].v));
}
}
}
int main()
{
//memset(dis,127,sizeof(dis));
//memset只能用来赋值为0,-1,其他数字用fill
fill(dis,dis+5005,INF);
//for(int i=0;i<=5005;i++) cout<<i<<' '<<dis[i]<<endl;;
memset(head,-1,sizeof(head));
scanf("%d%d",&n,&m);
for(R i=1;i<=m;i++){
scanf("%d%d%d",&ai,&bi,&ci);
add(ai,bi,ci);add(bi,ai,ci);
//无向图 Kruskal中无此步骤,因为是直接添边 ,Prim中以点找边就要这么处理
}
prim();
if(cnt==n) printf("%d",sum);
else printf("orz");
return 0;
}
方法三:Kruskal+快排
#include<bits/stdc++.h>
using namespace std;
int n,m,i,j,u,v,total;
struct edge{int start,to;long long val;}bian[2000005];
int f[100000];
long long ans;//找爹函数
int find(int x){return (f[x]==x)?x:f[x]=find(f[x]);}
bool cmp(edge a,edge b){return a.val<b.val;}
inline void kruskal()
{
for(int i=1;i<=m;i++)//遍历所有边
{
u=find(bian[i].start);v=find(bian[i].to);
if(u==v) continue;
ans+=bian[i].val;
f[u]=v;//并查集u、v的爹变成同一个爹操作
total++;
if(total==n-1) break;
}
}
int main()
{
scanf("%d%d",&n,&m);
for(i=1;i<=n;i++) f[i]=i;//并查集赋初值
for(i=1;i<=m;i++)
{
scanf("%d%d%d",&bian[i].start,&bian[i].to,&bian[i].val);
}
sort(bian+1,bian+m+1,cmp);
kruskal();
printf("%d",ans);
return 0;
}