最小生成树
树的遍历
BFS:一开始队列中有一个点,将一个点出队,将它的子结点全都入队。
DFS:递归到一个点时,依次递归它的子结点。
无根树变有根树
选择一个点作为根结点,开始遍历。
遍历到一个点时,枚举每一条连接它和另一个点的边。若另一个点不是它的父结点,那就是它的子结点。递归到子结点。
并查集
处理不相交合并和查询问题。
邻接表存图:
const int N=1005;
const int M=10050;
int point[N],to[M],next[M],cc
void AddEdge(int x,int y)
{
cc++;
to[cc]=y;
next[cc]=point[x];
point[x]=cc;
}
void find(int x)
{
int now=point[x];
while(now)
{
printf("%d\n",now);
now=next[now];
}
}
int main()
{
}
链式前向星
struct edge
{
int end;
edge *suc;
edge(int _end,edge *_suc):end(_end),suc(_suc){}
}*head[N];
void add_edge(int u,int v)
{
head[u]=new edge(v,head[u]);
}
最小生成树
稀疏图用kruskal,稠密图用prim
prim:
先随机找一个点x作为根,然后维护一个集合S(存已经连通的点)和一个集合D(存当前连接S中所有点的线段,两端点都在S中的除外)
初始化S={x},D={x所连接的所有边};
每次在D中选一个权值最小的边,然后将该边删去。该边所连接的另一个点放入S中,直到S中点数=全部点数。
这里记顶点数v,边数e
邻接表:O(elog2v)
kruskal:
将边权从小到大排序,每次选择边权最小的,如果当前边连接的点已经连通了,就不选这条边。
利用并查集维护是否连通。
e为图中的边数
邻接表:O(elog2e)
最小生成树:
#include<iostream>
#include<bits/stdc++.h>
using namespace std;
struct node{
int x,y,z;
}a[200005];
int n,m,ans;
int f[5010];
bool cmp(node x,node y){
return x.z<y.z;
}//快排按边权排序
int find(int x){
return f[x]==x?x:f[x]=find(f[x]);
}//并查集压缩路径
int main()
{
int n,m, cnt = 0;
cin>>n>>m;
for(int i=1;i<=m;i++){
cin>>a[i].x>>a[i].y>>a[i].z;
}
sort(a+1,a+m+1,cmp);//排序使权值从小到大顺序
for(int i=1;i<=n;i++){
f[i]=i;//找顶点,最开始为序号1;
}
for(int i=1;i<=m;i++){
if(find(a[i].x)!=find(a[i].y)){
cnt++;
ans+=a[i].z;
f[find(a[i].x)] = f[find(a[i].y)];
// f[f[a[i].x]]=f[a[i].y];
}
}
if(cnt == n-1) cout<<ans<<endl;
else cout<<"orz"<<endl;
return 0;
}
洛谷:买礼物[https://blog.csdn.net/qq_40400202/article/details/82355202]
kij =kji :无向图
建立的过程:要买所有的物品,把物品看做点,优惠看做边,然后建边,求一次最小生成树再加上第一件的A元就行了
1.Well
【题目描述】
缺水的村庄需要水。乡村规划者决定挖造若干口井来缓解缺水危机。一共有N个村庄,在第i号村庄挖井的代价是ai。为了节约财力,两个村庄均可以修建水管来共享水资源,以此来获得水源。第i个村庄与第j个村庄修建水管的代价为bij。若要让每个乡村都有水资源,请问需要最小花费代价是多少。
【输入格式】
第一行两个正整数N。
第二行N个正整数依次表示在1到N号村庄挖井代价。
接下来N行,每行N个正整数,第i行第j列的数字w表示从第i号村庄到第j号村庄修建代价为w(保证bij = bji且bii = 0)。
【输入格式】
一个正整数代表最小花费代价。
【输入样例】
4
5 4 4 3
0 2 2 2
2 0 3 3
2 3 0 4
2 3 4 0
【输出样例】
9
【数据范围】
1 <= N,ai,bij <= 1000
#include <algorithm>
#include <iostream>
#include <cstdio>
using namespace std;
int B,tot,sum,num,now=1;
int st[10010];
int father[10010];
//最小生成树用来解决最小代价如何用最小的“代价”用N-1条边连接N个点的问题.
struct Node
{
int th1;//顶点
int th2;//边
int pri;//权值
}rec[1000000];
bool cmp(Node a,Node b)
{
return a.pri<b.pri;//sort()函数,Sort(start,end,cmp),cmp用于规定排序的方法,可不填,默认升序。
}
int find(int x)//压缩路径,使的每次寻找的时候只有一个
{
if( x==father[x] )
return x;
else
return father[x]=find( father[x] );
}
/*int find(int x){
return f[x]==x?x:f[x]=find(f[x]);
}//并查集压缩路径
*/
void unite(int x,int y)//按顺序枚举每一条边。如果这条边连接着两个不同的集合,那么就把这条边加入最小生成树,这两个不同的集合就合并成了一个集合;如果这条边连接的两个点属于同一集合,就跳过。直到选取了n-1条边为止。
{
x=find(x);
y=find(y);
father[x]=y;
}
int main()
{
cin>>B;
for(int i=1; i<=B; i++)
father[i]=i;
for(int i=1;i<=B;i++){
cin>>st[i];
}
for(int i=0; i<=B; i++)
for(int j=1; j<=B; j++)
{
if(i==0)
{
rec[now].th1=0;
rec[now].th2=j;
rec[now].pri=st[j];
now++;
continue;
}
cin>>num;
if(num==0)continue;
rec[now].th1=i;
rec[now].th2=j;
rec[now].pri=num;
now++;
}
sort( rec+1,rec+now,cmp );
for(int i=1; i<now; i++)
if( find(rec[i].th1) != find(rec[i].th2) )
{
unite(rec[i].th1 , rec[i].th2);
tot+=rec[i].pri;
sum++;
}
cout<<tot;
return 0;
}
一、关于并查集
-
定义
并查集(Disjoint-Set)是一种可以动态维护若干个不重叠的集合,并支持合并与查询两种操作的一种数据结构。 -
基本操作
-
合并(Union/Merge)1:合并两个集合。
-
查询(Find/Get):查询元素所属集合。
实际操作时,我们会使用一个点来代表整个集合,即一个元素的根结点(可以理解为父亲)。 -
具体实现
我们建立一个数组fa[ ]或pre[ ]表示一个并查集,fa[i]表示i的父节点。
初始化:每一个点都是一个集合,因此自己的父节点就是自己fa[i]=i
查询:每一个节点不断寻找自己的父节点,若此时自己的父节点就是自己,那么该点为集合的根结点,返回该点。
修改:合并两个集合只需要合并两个集合的根结点,即fa[RootA]=RootB,其中RootA,RootB是两个元素的根结点。
路径压缩:
实际上,我们在查询过程中只关心根结点是什么,并不关心这棵树的形态(有一些题除外)。因此我们可以在查询操作的时候将访问过的每个点都指向树根,这样的方法叫做路径压缩,单次操作复杂度为O(logN)。
结合下图食用更好(图为状态压缩的过程):
图片3.