图论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;
}

 

posted @ 2020-03-27 15:11  CZD648  阅读(241)  评论(0编辑  收藏  举报
Live2D