概念:
无向图中,其某个子图中任意两个顶点都互相连通并且是一棵树,称之为生成树;若每个顶点有权值,则其权值和最小的生成树为最小生成树。
适用范围:
能用图表示,求其权值和最小(或次小等)。
算法:
(1) prim算法
参考自白皮p105.
类比Dijkstra算法,从某个顶点出发,不断加边。
int vis[MAXN]; //标记数组,表该点是否已经在集合中
int cost[MAXN][MAXN]; //图,下标从0开始 ,到n-1
int lw[MAXN]; // 最短路径
int prim(int n)
{
int ans=0;
memset(vis,0,sizeof(vis));
vis[0]=1;
for(int i=1;i<n;i++){
int mi=INF,p=-1;
for(int j=0;j<n;j++)
if(!vis[j]&&mi>lw[j])
{
p=j;
mi=lw[j];
}
if(mi==INF) return -1 //原图不连通
ans+=mi;
for(int j=0;j<n;j++)
if(!vis[j]&&lw[j]>cost[p][j])
lw[j]=cost[p][j];
}
return ans;
}
(2) Kruskal算法
将所有边的权值排序,用并查集判读是否连通,不连通则加边。
struct edge
{
int u,v,cost;
}x[MAXN;
int fa[MAXN];
int find(int a)
{
return fa[a]==-1?a:fa[a]=find(fa[a]);
}
bool cmp(struct edge a,struct edge b)
{
return a.cost<b.cost;
}
void unite(int a,int b)
{
int f=find(a),fb=find(b);
if(f!=fb)
fa[fb]=f;
}
bool same(int a,int b)
{
return find(a)==find(b);
}
int kruskal()
{
int ans=0;
ini(); //初始化
sort(x,x+sum,cmp);
for(int i=0;i<n;i++)
{
if(!same(x[i].u,x[i].v))
{
unite(x[i].u,x[i].v);
ans+=x[i].cost;
}
}
return ans;
}
例题:
(1)poj-1251
最小生成树基本模板题
struct stu
{
int u;
int v;
int cost;
}edge[2500];
int n,fa[100];
char ch[3],s[3];
void ini()
{
for(int i=0;i<=70;i++)
fa[i]=i;
}
int find(int x)
{
return fa[x]==x?x:fa[x]=find(fa[x]);
}
void unite(int a,int b)
{
int x=find(a),y=find(b);
if(x!=y) fa[y]=x;
}
bool same(int a,int b)
{
return find(a)==find(b);
}
bool cmp(stu a,stu b)
{
return a.cost<b.cost;
}
int main()
{
while(scanf("%d",&n)!=EOF&&n)
{
int m,sum=0,ans=0;
memset(edge,0,sizeof(edge));
for(int j=0;j<n-1;j++){
scanf("%s%d",s,&m);
for(int k=0;k<m;k++){
scanf("%s%d",ch,&edge[sum].cost);
edge[sum].u=s[0]-'A';
edge[sum].v=ch[0]-'A';
//cout<<x[sum].u<<" "<<x[sum].v<<" "<<x[sum].cost<<endl;
sum++;
}
}
ini();
sort(edge,edge+sum,cmp);
for(int i=0;i<sum;i++){
stu t=edge[i];
if(!same(t.u,t.v))
{
unite(t.u,t.v);
ans+=t.cost;
}
}
cout<<ans<<endl;
}
return 0;
}
(2) 畅通工程再续
根据题干,最多一百个点,所以最多100*100/2条边,可以直接建边,就变成了最小生成树模板题。
注意分析数据范围!
注意点的数目和构造边,判读数据大小
struct stu
{
int u;
int v;
double cost;
}edge[21000];
int x[110],y[110],fa[11000],c;
bool vj(int i,int j)
{
int t=(x[i]-x[j])*(x[i]-x[j])+(y[i]-y[j])*(y[i]-y[j]);
if(t>=100&t<=1000000)
return true;
else
return false;
}
bool cmp(stu a,stu b)
{
return a.cost<b.cost;
}
int find(int a)
{
return fa[a]==-1?a:fa[a]=find(fa[a]);
}
bool same(int a,int b)
{
return find(a)!=find(b);
}
void unite(int a,int b)
{
int f=find(a),f1=find(b);
if(f!=f1)
fa[f1]=f;
}
int main()
{
int t;
cin>>t;
while(t--)
{
memset(edge,0,sizeof(edge));
scanf("%d",&c);
memset(fa,-1,sizeof(fa));
for(int i=0;i<c;i++){
scanf("%d%d",&x[i],&y[i]);
}
int sum=0;
for(int i=0;i<c-1;i++){
for(int j=i+1;j<c;j++){
if(vj(i,j))
{
edge[sum].u=i;edge[sum].v=j;
edge[sum].cost=sqrt((x[i]-x[j])*(x[i]-x[j])+(y[i]-y[j])*(y[i]-y[j]));
//cout<<edge[sum].u<<" "<<edge[sum].v<<" "<<edge[sum].cost<<endl;
sum++;
}
}
}
if(sum==0)
{
printf("oh!\n");
continue;
}
double ans=0;
sort(edge,edge+sum,cmp);
int cnt=0;
for(int i=0;i<sum;i++){
if(same(edge[i].u,edge[i].v))
{
ans+=edge[i].cost;
unite(edge[i].u,edge[i].v);
cnt++;
}
}
if(cnt!=c-1)
printf("oh!\n");
else
{
ans*=100;
printf("%.1lf\n",ans);
}
}
return 0;
}
(3) Building a Space Station
需要先读懂题意。。。题意是给你一个球的球心坐标和半径r,两个球相交或相切则距离为零,否则为球心距离减两个球的半径。然后又变成了最小生成树模板题。
struct stu
{
double x,y,z,r;
}pos[11000];
struct edg
{
double u,v,cost;
}edge[11000];
int fa[11000];
bool check(int a,int b)
{
return pos[a].r+pos[b].r>=sqrt((pos[a].x-pos[b].x)*(pos[a].x-pos[b].x)+(pos[a].y-pos[b].y)*(pos[a].y-pos[b].y)+(pos[a].z-pos[b].z)*(pos[a].z-pos[b].z));
}
bool cmp(struct edg a,struct edg b)
{
return a.cost<b.cost;
}
int find(int a)
{
return fa[a]==-1?a:fa[a]=find(fa[a]);
}
void unite(int a,int b)
{
int x=find(a),y=find(b);
if(x!=y)
fa[y]=x;
}
bool same(int a,int b)
{
return find(a)==find(b);
}
int main()
{
int n;
while(scanf("%d",&n)&&n)
{
memset(fa,-1,sizeof(fa));
for(int i=0;i<n;i++)
scanf("%lf%lf%lf%lf",&pos[i].x,&pos[i].y,&pos[i].z,&pos[i].r);
int sum=0;
for(int i=0;i<n;i++){
for(int j=i+1;j<n;j++){
if(check(i,j))
{
edge[sum].u=i;
edge[sum].v=j;
edge[sum].cost=0;
sum++;
}
else
{
edge[sum].u=i;
edge[sum].v=j;
edge[sum].cost=sqrt((pos[i].x-pos[j].x)*(pos[i].x-pos[j].x)+(pos[i].y-pos[j].y)*(pos[i].y-pos[j].y)+(pos[i].z-pos[j].z)*(pos[i].z-pos[j].z))-pos[i].r-pos[j].r;
sum++;
}
}
}
double ans=0;
sort(edge,edge+sum,cmp);
for(int i=0;i<sum;i++){
if(!same(edge[i].u,edge[i].v))
{
unite(edge[i].u,edge[i].v);
ans+=edge[i].cost;
}
}
printf("%.3lf\n",ans);
}
return 0;
}
(4)Constructing Roads
修路,求最少花费,模板中的模板,之后的q组输入表示a和b之间已经有路,将其权值改成0就行。
struct stu
{
int u,v,cost;
}x[11000];
int fa[11000];
int find(int a)
{
return fa[a]==-1?a:fa[a]=find(fa[a]);
}
bool cmp(struct stu a,struct stu b)
{
return a.cost<b.cost;
}
void unite(int a,int b)
{
int f=find(a),fb=find(b);
if(f!=fb)
fa[fb]=f;
}
bool same(int a,int b)
{
return find(a)==find(b);
}
int map1[1100][1100],n,t;
int main()
{
cin>>n;
memset(fa,-1,sizeof(fa));
memset(x,-1,sizeof(x));
memset(map1,-1,sizeof(map1));
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
scanf("%d",&map1[i][j]);
cin>>t;
for(int i=0;i<t;i++){
int a,b;
scanf("%d%d",&a,&b);
map1[a-1][b-1]=map1[b-1][a-1]=0;
}
int sum=0;
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
if(i!=j)
{
x[sum].u=i;
x[sum].v=j;
x[sum].cost=map1[i][j];
sum++;
}
long long ans=0;
sort(x,x+sum,cmp);
for(int i=0;i<sum;i++){
if(!same(x[i].u,x[i].v))
{
unite(x[i].u,x[i].v);
ans+=(long long)x[i].cost;
}
}
cout<<ans<<endl;
return 0;
}
(5) Truck History
题目很长不好理解。。其实就是给你n个字符串,用一个7位的字符串代表一个编号,两个编号之间的distance代表这两个编号之间不同字母的个数。一个编号只能由另一个编号“衍生”出来,代价是这两个编号之间相应的distance,现在要找出一个“衍生”方案,使得总代价最小,也就是distance之和最小。
牵扯到了总distance最小,每个distance又是表示两个编号之间的,可以往建图方向考虑,自然也就想到了最小生成树。
string ch[3100];
int n;
//int map[3100][3100];
struct stu
{
int u,v,cost;
}x[2000000]; //注意范围2000*2000会爆
int sum,fa[2000000],ans;
bool cmp(struct stu a,struct stu b)
{
return a.cost<b.cost;
}
int find(int a)
{
return fa[a]==-1?a:fa[a]=find(fa[a]);
}
bool same(int a,int b)
{
return find(a)==find(b);
}
void unite(int a,int b)
{
int f=find(a),fb=find(b);
if(f!=fb)
fa[fb]=f;
}
int main()
{
while(scanf("%d",&n)&&n)
{
sum=ans=0;
memset(fa,-1,sizeof(fa));
memset(x,0,sizeof(x));
for(int i=0;i<n;i++)
cin>>ch[i];
for(int i=0;i<n;i++){
for(int j=i;j<n;j++){
int now=0;
for(int k=0;k<7;k++){
if(ch[i][k]!=ch[j][k])
{
now++;
}
}
x[sum].u=i;
x[sum].v=j;
x[sum].cost=now;
sum++;
}
}
sort(x,x+sum,cmp);
for(int i=0;i<sum;i++){
if(!same(x[i].u,x[i].v))
{
unite(x[i].u,x[i].v);
ans+=x[i].cost;
}
}
printf("The highest possible quality is 1/%d.\n",ans);
}
return 0;
}