zoukankan      html  css  js  c++  java
  • 图论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;
    }
  • 相关阅读:
    Idea中重建maven模块,dependencies引入为空的解决办法
    HTML <base> 标签 为页面上的所有链接规定默认地址或默认目标
    HTML 5 视频
    JavaScript 使用反斜杠对代码行进行折行
    HTML <b>、 <strong> 、<big>、<small>、<em>、<i>、<sub>和<sup> 标签
    JavaScript concat() 方法-连接两个或多个数组
    JavaScript Array(数组)对象
    JavaScript indexOf() 方法和 lastIndexOf() 方法
    JavaScript String(字符串)对象 实例
    JavaScript 对象
  • 原文地址:https://www.cnblogs.com/chen-1/p/12581606.html
Copyright © 2011-2022 走看看