图论day2(回家/香甜的黄油/bellman-ford算法的理解)

几乎没有听懂的SPFA&听懂一点点的MST&好像听懂了的拓扑排序。

……然后我先过了一道floyed。floyed的思想还是挺好理解的,类似传递闭包。不过k循环为什么放最外面,等涉及动规了再看吧。
luogu1529.回家。

现在是晚餐时间,而母牛们在外面分散的牧场中。 农民约翰按响了电铃,所以她们开始向谷仓走去。 你的工作是要指出哪只母牛会最先到达谷仓(在给出的测试数据中,总会有且只有一只最快的母牛)。 在挤奶的时候(晚餐前),每只母牛都在她自己的牧场上,一些牧场上可能没有母牛。 每个牧场由一条条道路和一个或多个牧场连接(可能包括自己)。 有时,两个牧场(可能是字母相同的)之间会有超过一条道路相连。 至少有一个牧场和谷仓之间有道路连接。 因此,所有的母牛最后都能到达谷仓,并且母牛总是走最短的路径。 当然,母牛能向着任意一方向前进,并且她们以相同的速度前进。 牧场被标记为’a’..’z’和’A’..’Y’,在用大写字母表示的牧场中有一只母牛,小写字母中则没有。 谷仓的标记是’Z’,注意没有母牛在谷仓中。

#include<bits/stdc++.h>
using namespace std;

int n;
int a[200][200];//存图。
int m[200]={};//存哪里有奶牛。

int main()
{
    memset(a,10,sizeof(a));//初赋值极大。
    scanf("%d\n",&n);
    for(int i=1;i<=n;i++)
    {
        char ch1,ch2,hhe;int x;
        if(i<n) scanf("%c %c %d\n",&ch1,&ch2,&x);
        else scanf("%c %c %d",&ch1,&ch2,&x);//这里蛋疼的输入……肯定有简单得多的办法,然而……
        a[ch1][ch2]=min(a[ch1][ch2],x);//因为两个牧场之间可能有多条,当然是取最短的。
        a[ch2][ch1]=min(a[ch2][ch1],x);
        if(ch1<='Z'&&ch1>='A') m[ch1]=1;//如果有奶牛,要存一下。
        if(ch2<='Z'&&ch2>='A') m[ch2]=1;
    }
    for(int i='A';i<='z';i++) a[i][i]=0;//自己到自己路径为零。
    for(int k='A';k<='z';k++)
        for(int i='A';i<='z';i++)
            for(int j='A';j<='z';j++)
                if(a[i][k]+a[k][j]<a[i][j]) a[i][j]=a[i][k]+a[k][j];//……floyed。
    int minn=1000000;
    char num;
    for(int i='A';i<='Y';i++)
        if(m[i]==1&&a['Z'][i]<minn) minn=a['Z'][i],num=(char)i;//找最小值。注意这里的判断条件必须有判断小于的那一步,不能偷懒直接在后面用minn=min(a['Z'][i],minn),因为这样num的值就不是最小那个的编号了……
    cout<<num<<" "<<minn;
    return 0;
}

数组下标直接用a[‘A’]这种方式简单很多,但是此时下标是’A’的编码,所以数组要开大一点。

兴奋到难以言喻,终于看懂bellman-ford了!!!!!感谢ycy的悉心讲解(……)

luogu1828.香甜的黄油

农夫John发现做出全威斯康辛州最甜的黄油的方法:糖。把糖放在一片牧场上,他知道N(1<=N<=500)只奶牛会过来舔它,这样就能做出能卖好价钱的超甜黄油。当然,他将付出额外的费用在奶牛上。
农夫John很狡猾。像以前的Pavlov,他知道他可以训练这些奶牛,让它们在听到铃声时去一个特定的牧场。他打算将糖放在那里然后下午发出铃声,以至他可以在晚上挤奶。
农夫John知道每只奶牛都在各自喜欢的牧场(一个牧场不一定只有一头牛)。给出各头牛在的牧场和牧场间的路线,找出使所有牛到达的路程和最短的牧场(他将把糖放在那)

思路:挨个点算最短路径和。
个人理解,因为这道题并不需要进行插入删除等操作,只需要对边逐个枚举,所以没有必要用邻接表,边表(只存一条边的起点、终点、权值)就okey了(因为简单)。……一切视情况而定。

关于bellman-ford算法。

基本思想:n个点的图,最坏的情况即最短路径包含n个点,即有n-1条边。假设初始点一开始到每个点的距离都无穷大(当然除了自己)。在每次大循环(1~n-1)中枚举所有的边(1<=j<=边数)并松弛,在这个松弛过程中,和上次更新过的的点相邻的点都是一定可以被更新的,而和它不相邻的点是一定不会被更新的。这也是在SPFA中它可以用队列维护的原因。

和dijkstra相比,那个是逐步求最短的贪心算法,而bellman-ford则是不断接近最短路径的算法。
有个很简单的优化:如果某次松弛后没有任何值改变(即没有值需要松弛),那就说明最短路径已经躺在数组里,可以直接break了。
理解可能还有不透彻的地方,慢慢改进。

#include<bits/stdc++.h>
using namespace std;

struct edge
{
    int q,z;//起点、终点。 
    int v;//权值。 
}e[3000]; 
int n,p,c;
int tot=0; 
int sum[1000]={};//每个牧场存奶牛数。
int dis[1000];
int minn=10000000;//所需的最短路径。 

void Bellman(int x)
{
    memset(dis,10,sizeof(dis));//一开始假设到每个点都无——穷——大。
    dis[x]=0;
    for(int i=1;i<p;i++)//最多迭代p-1次。 
    {
        int flag=0;//0-本次无边被松弛。
        for(int j=1;j<=c*2;j++)//枚举每一条边。
           if(dis[e[j].q]+e[j].v<dis[e[j].z])//如果起始点到第j条边起点的路径长+这条边的权值<开始点到第k条边终点的路径长,那就要松弛了。而且要标记! 
               dis[e[j].z]=dis[e[j].q]+e[j].v,flag=1;
        if(flag==0) break;//其实这是一个优化。如果没有边能被松弛了,就说明已经最优啦。 
    }  
    int ss=0;//存每次的总路径长。
    for(int i=1;i<=p;i++) ss+=dis[i]*sum[i];//i点的奶牛数* i点到开始点的距离。
    minn=min(minn,ss); 
    return;
}

int main()
{
    scanf("%d%d%d",&n,&p,&c);
    for(int i=1;i<=n;i++)
    {
        int x;
        scanf("%d",&x);
        sum[x]++;
    }//输入+存奶牛所在牧场。
    for(int i=1;i<=c;i++) 
    {
        int x,y,v;
        scanf("%d%d%d",&x,&y,&v);
        //赌一把两个牧场之间最多只有一条路,就不判断了。
        e[++tot].q=x;e[tot].z=y;e[tot].v=v;
        e[++tot].q=y;e[tot].z=x;e[tot].v=v;
    }
    for(int i=1;i<=p;i++) Bellman(i);
    printf("%d",minn);//终于熬到输出的快乐时刻了。……!!!这一刻,吃到香甜的黄油。……
    return 0;
}

明天就要开始正常上课了,有点舍不得这样大把时光编程的日子……T T。希望自己文化课加油……!

    原文作者:Bellman - ford算法
    原文地址: https://blog.csdn.net/hfl030/article/details/77942970
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞