图论6-最小生成树变形2
上次讲了最小生成树的变形之克鲁斯卡尔,但是还有一种非常著名的最小生成树算法,也就是prim(普里姆),要求要大概了解prim的工作流程。
如果非常了解prim就可以跳过下一段。
prim算法流程:有一个数组d表示每个点到集合的距离,而不像dijkstra那样到源点的距离,最开始,集合中只有1号节点,然后从1号节点扩展出
到所有节点的d值,特别的,如果一个节点没有路可以到达集合,则d的值为+∞,为了方便,就是0x3f3f3f3f了。然后每次将离集合最近的且不在
集合中的点加入集合,让后从这个点往所有点扩展,更新d值,重复以上过程直到所有点都在集合中了。
对prim的证明:可以使用数学归纳法。
命题1.证明对于一个已经确定是其中一个最小生成树点、边的子集(通俗的说就是最小生成树包含这个图),向其中添加离这个集合最近的点及其连边
仍是其中一个最小生成树的点边子集。
证明:使用反证法,先设这个离集合最近的点是s,其距离是val_s,因为是最小生成树,所以s必定是一个最小生成树的点边的子集。如果不取集合
到这个点的连边,假设取了集合另一个点t,这个边长val_t,可以得知val_t>val_。而s必定要被取,所以必须到t之后再经过一些边才能再到点s,所
一花费就是(设图中经过的边的花费总和为x)x+val_t,但是如果取集合与s的连边再取这这些边(因为t-s与s-t是一样的,所以必定经过这个x),
总价值就是val_s+x,因为val_s<val_t,所以val_s+x<val_t+x,所以不如取val_s。
如图:
命题1得证
命题2.一号节点是一颗最小生成树的点边子集
证明:因为一号节点属于任意最小生成树的点集,一个最小生成树的点集属于这个最小生成树的点边集,故一号节点属于任意最小生成树的点边集
命题2得证
故prim算法正确性得证
通过数学归纳法,我们证明了prim算法的正确性,现在,我们要将prim用到题目中。
今天的题是 四叶草魔杖
大致题意:有n点内有数,保证所有点内的数之和为0,有m连边待开通,第i条边开通的花费是vi,一条边开通后,这两个点i,j的数值就能随意变化
但要保证变化完两数的和还是ai+aj,问至少花费多少才能使每个点的数都能变成0。
思路分析:先要明白一件事,那就是如果i,j,k之间能连通,则i,j,k上的数就能在满足和为ai+aj+ak的情况下随意变化,所以如果让所有点的数都能变为
0,那么就要整个图连通,所以就求最小生成树即可。但真的这么简单吗?如果这样就行了的话可以为什么还要告诉我们每个点的数呢?通过这一点就
可以让我们意识到这种做法不完全对,因为有个特例,那就是“0”,如果一个节点的权值已经是0,那就不需要连边了,所以我们先特判出来所有0
的节点然后标记,之后这些点不用连边了。之后同理如果有几个点内的数和为0也可用2^n同样处置。最后再跑一遍prim就好了。(因为数据弱所以
本蒟蒻只写了判断单点为0的代码,毕竟今天的主题是prim嘛)。
#include<bits/stdc++.h> using namespace std; const int NR=20; const int MR=NR*NR; const int INF=0x3f3f3f3f; int n,m; int num; int a[NR]; bool flag[NR]; int dis[NR][NR]; int d[NR]; int ans=0; void prim()//高潮Prim! { int k=n+1,rt=0;//把变量都定义好 memset(d,0x3f,sizeof(d));//数组初始化 d[1]=0;while(flag[++rt]);//计算rt for(int i=rt;i<=n-num;i++)//枚举 { k=n+1;//k最开始置为n+1 for(int j=1;j<=n;j++) { if(flag[j]) continue;//如果在集合内直接跳过 if(d[j]<d[k]) k=j;//打擂台 } flag[k]=1;//标记入集合 ans+=d[k];//统计答案 for(int j=1;j<=n;j++) { d[j]=min(d[j],dis[k][j]);//更新d数组 } } } int read() { int x=0,f=1;char ch=getchar(); while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();} while(ch<='9'&&ch>='0'){x=(x<<3)+(x<<1)+(ch^48);ch=getchar();} return x*f; } int main() { // freopen("1.in","r",stdin); // freopen("1.out","w",stdout); memset(dis,0x3f,sizeof(dis)); n=read(),m=read(); for(int i=1;i<=n;i++) { a[i]=read(); flag[i]=(!a[i]); num+=flag[i]; } for(int i=1;i<=m;i++) { int x=read(),y=read(),z=read(); x++,y++; dis[x][y]=dis[y][x]=z; } for(int i=1;i<=n;i++) { if(!flag[i]) continue; for(int x=1;x<=n;x++) { for(int y=1;y<=n;y++) { dis[x][y]=min(dis[x][y],dis[x][i]+dis[i][y]);//算出任意两点间的距离 } } } prim(); if(ans<0||ans>INF) { puts("Impossible"); return 0; } printf("%d",ans); return 0; }