最短路问题

Posted mgtnb

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了最短路问题相关的知识,希望对你有一定的参考价值。

 


 算法汇总:

  • Floyd 算法

  • Dijkstra 算法

  • Bellman-Ford 算法

  • SPFA算法


Floyd算法

应该是几个算法当中最简单的了,虽然时间复杂度有点高。

f[k][i][j]表示从i经过若干个编号不超过k的节点到j的最短路长度。于是有:

                 f[k][i][j]=min(f[k-1][i][j],f[k-1][i][k]+f[k-1][k][j])

k这一维可以被省略,对结果没有影响。

                 f[i][j]=min(f[i][j],f[i][k]+f[k][j])

传递闭包

通过传递性推导出尽量多的元素之间的关系。

核心代码:

for(int i=1;i<=n;i++) f[i][i]=1;
for(int k=1;k<=n;k++)
{
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=n;j++)
        {
            f[i][j]|=f[i][k]&f[k][j];
        } 
    }
}

Sorting It All Out

描述

An ascending sorted sequence of distinct values is one in which some form of a less-than operator is used to order the elements from smallest to largest. For example, the sorted sequence A, B, C, D implies that A < B, B < C and C < D. in this problem, we will give you a set of relations of the form A < B and ask you to determine whether a sorted order has been specified or not.

输入Input consists of multiple problem instances. Each instance starts with a line containing two positive integers n and m. the first value indicated the number of objects to sort, where 2 <= n <= 26. The objects to be sorted will be the first n characters of the uppercase alphabet. The second value m indicates the number of relations of the form A < B which will be given in this problem instance. Next will be m lines, each containing one such relation consisting of three characters: an uppercase letter, the character "<" and a second uppercase letter. No letter will be outside the range of the first n letters of the alphabet. Values of n = m = 0 indicate end of input.输出For each problem instance, output consists of one line. This line should be one of the following three:

Sorted sequence determined after xxx relations: yyy...y.
Sorted sequence cannot be determined.
Inconsistency found after xxx relations.

where xxx is the number of relations processed at the time either a sorted sequence is determined or an inconsistency is found, whichever comes first, and yyy...y is the sorted, ascending sequence.

样例输入

4 6

A<B

A<C

B<C

C<D

B<D

A<B

3 2

A<B

B<A

26 1

A<Z

0 0

样例输出

Sorted sequence determined after 4 relations: ABCD.

Inconsistency found after 2 relations.

Sorted sequence cannot be determined.

 

这是一道有向的传递闭包问题,对于每个i<j的式子,令f[i][j]=1,其余都=0。

用Floyd进行传递闭包后,判断如果有f[i][j]=f[j][i]=1,则说明矛盾。输出Inconsistency found after xxx relations.

判断每个点的出度和入度并保存下来,如果加起来不等于n-1,则说明不能确定每一对的大小关系。输出Sorted sequence cannot be determined.

除此之外,可以用拓扑排序,把每个点的顺序保存下来。再输出。

最后附上我没有AC的代码(我真的不知道哪里错了),康康就行了,不要相信它。

#include<bits/stdc++.h>
using namespace std;
int n,m,tot;
bool f[30][30],vis[30];
int in[30],out[30];
char sh[30];

bool floyd()
{
    for(int k=1;k<=n;k++)
    {
        for(int i=1;i<=n;i++)
        {
            for(int j=1;j<=n;j++)
            {
                if(f[i][k]&&f[k][j]) f[i][j]=1;
            } 
        }
    }
    for(int i=1;i<n;i++)
    {
        for(int j=i+1;j<=n;j++)
        {
            if(f[i][j]==1&&f[j][i]==1) return 0;
        }
    }
    return 1;
}

bool check()
{
    memset(in,0,sizeof(in));
    memset(out,0,sizeof(out));
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=n;j++)
        {
            if(f[i][j]) in[j]++,out[i]++;
        }
    }
    for(int i=1;i<=n;i++)
    if(in[i]+out[i]!=n-1) return 0;
    else return 1;
}

void topsort()
{
    stack<int> q;
    int i;
    memset(vis,0,sizeof(vis));
    tot=0;
    for(i=1;i<=n;i++)
    {
        if(in[i]==0)
        {
            q.push(i);
            break;
        }
    }
    while(!q.empty())
    {
        int cur=q.top();
        q.pop();
        vis[cur]=1;
        sh[tot++]=cur-1+A;
        for(int i=1;i<=n;i++)
        {
            if(vis[i]==0&&f[cur][i]) in[i]--;
            if(vis[i]==0&&in[i]==0&&i!=cur) q.push(i);
        }
    }
}

int read()
{
    int f=1,x=0;
    char c;
    c=getchar();
    while(c<0||c>9)
    {
        if(c==-) f=-1;
        c=getchar();
    }
    while(c>=0&&c<=9)
    {
        x=x*10+c-0;
        x=x%10000007;
        c=getchar();
    }
    return x*f;
}

int main()
{
    while(1)
    {
        n=read();m=read();
        if(n==0&&m==0) break;
        memset(f,0,sizeof(f));
        int flag1=0,flag2=0;
        
        for(int i=1;i<=m;i++)
        {
            char a,b,c;
            cin>>a>>b>>c;
            f[a-A+1][c-A+1]=1;
            if(flag1||flag2) continue;
            else if(floyd()==0)
            {
                flag1=i;
                continue;
            }
            else if(check())
            {
                topsort();
                flag2=i;
            }
        }
        if(flag1) printf("Inconsistency found after %d relations.
",flag1);
        else if(flag2)     printf("Sorted sequence determined after %d relations: %s.
",flag2,sh);
        else printf("Sorted sequence cannot be determined.
");
    }
    return 0;
} 

 


Dijkstra 算法

算法流程:

1.初始化dis[1]=0,其余节点的dis值为正无穷大。

2.找出一个没有被标记的、dis[x]最小的节点x,再标记。

3.找节点x的所有出边y,dis[y]=min(dis[y],dis[x]+z)。

4.重复上述步骤,直到所有点都被标记。

注意Dijkstra算法不适用于负权边。

贴上模板 时间复杂度O(N2)

void dijkstra()
{
    memset(dis,0x3f,sizeof(dis));
    memset(vis,0,sizeof(cis));
    dis[1]=0;
    for(int i=1;i<n;i++)
    {
        int x=0;
        for(int j=1;j<=n;j++)
        if(vis[j]==0&&(x==0||dis[j]<dis[x])) x=j;
        vis[x]=1;
        for(int y=1;y<=n;y++)
        dis[y]=min(dis[y],dis[x]+a[x][y]);
    }
}

堆优化(优先队列)

O(mlogn)

priority_queue< pair<int,int> >q;
void dijkstra()
{
    memset(dis,0x3f,sizeof(dis));
    memset(vis,0,sizeof(vis));
    dis[1]=0;
    q.push(make_pair(0,1));
    while(q.size())
    {
        int x=q.top().second;q.pop();
        if(vis[x]) continue;
        vis[x]=1;
        for(int i=head[x];i;i=next[i])
        {
            int y=ver[i],z=edge[i];
            if(dis[y]>dis[x]+z)
            {
                dis[y]=dis[x]+z;
                q.push(make_pair(-d[y],y));
            }
        }
    }
}

Bellman-Ford 算法

1.扫描所有边(x,y,z),若dis[y]>dis[x]+z,则用dis[x]+z更新dis[y]。

2.重复上述步骤,直到没有更新操作发生。

时间复杂度为O(nm)。

SPFA 算法

队列优化的Bellman-man算法

1.建立一个队列,初始队列只有起点。

2.取出队头结点x,扫描它的所有出边(x,y,z),若dis[y]>dis[x]+z,则用dis[x]+z更新dis[y]。如果y不在队列中,则把y入队。

3.重复直到队列为空。

时间复杂度 O(km)~O(nm)。

queue<int> q;
void spfa()
{
    memset(dis,0x3f,sizeof(dis));
    memset(vis,0,sizeof(vis));
    dis[1]=0;vis[1]=1;
    q.push(1);
    while(q.size())
    {
        int x=q.front();q.pop();
        vis[x]=0;
        for(int i=head[x];i;i=next[i])
        {
            int y=ver[i],z=edge[i];
            if(dis[y]>dis[x]+z)
            {
                dis[y]=dis[x]+z;
                if(!vis[y]) q,push(y),vis[y]=1;
            }
        }
    }
}

在有负权边的情况下,Bellman-Ford和SPFA算法也能使用。

最优贸易

描述

C国有 n 个大城市和 m 条道路,每条道路连接这 n 个城市中的某两个城市。任意两个城市之间最多只有一条道路直接相连。这 m 条道路中有一部分为单向通行的道路,一部分为双向通行的道路,双向通行的道路在统计条数时也计为1条。
C国幅员辽阔,各地的资源分布情况各不相同,这就导致了同一种商品在不同城市的价格不一定相同。但是,同一种商品在同一个城市的买入价和卖出价始终是相同的。
商人阿龙来到C国旅游。当他得知“同一种商品在不同城市的价格可能会不同”这一信息之后,便决定在旅游的同时,利用商品在不同城市中的差价赚一点旅费。设C国 n 个城市的标号从 1~n,阿龙决定从1号城市出发,并最终在 n 号城市结束自己的旅行。在旅游的过程中,任何城市可以被重复经过多次,但不要求经过所有 n 个城市。
阿龙通过这样的贸易方式赚取旅费:他会选择一个经过的城市买入他最喜欢的商品——水晶球,并在之后经过的另一个城市卖出这个水晶球,用赚取的差价当做旅费。因为阿龙主要是来C国旅游,他决定这个贸易只进行最多一次,当然,在赚不到差价的情况下他就无需进行贸易。
现在给出 n 个城市的水晶球价格,m 条道路的信息(每条道路所连接的两个城市的编号以及该条道路的通行情况)。请你告诉阿龙,他最多能赚取多少旅费。

输入格式

   第一行包含 2 个正整数n 和m,中间用一个空格隔开,分别表示城市的数目和道路的
数目。
   第二行 n 个正整数,每两个整数之间用一个空格隔开,按标号顺序分别表示这n 个城
市的商品价格。
   接下来 m 行,每行有3 个正整数,x,y,z,每两个整数之间用一个空格隔开。如果z=1,表示这条道路是城市x 到城市y 之间的单向道路;如果z=2,表示这条道路为城市x 和城市y 之间的双向道路。

输出格式

一个整数,表示答案。

样例输入

5 5

4 3 5 6 1

1 2 1

1 4 1

2 3 2

3 5 1

4 5 2

样例输出

5

数据范围与约定

输入数据保证 1 号城市可以到达n 号城市。
对于 10%的数据,1≤n≤6。
对于 30%的数据,1≤n≤100。
对于 50%的数据,不存在一条旅游路线,可以从一个城市出发,再回到这个城市。
对于 100%的数据,1≤n≤100000,1≤m≤500000,1≤x,y≤n,1≤z≤2,1≤各城市
水晶球价格≤100。

 

题目大概意思就是说在带权值的图上找到一条从1到n的路径,使有两个点的权值差最大。

先以1为起点,在原图上用SPFA,保存下一个数组d,表示从1到x的所有路径中,能够经过的权值最小的节点的权值。

在求单源最短路径时,用min(d[x],price[y])更新d[y];

再以n为起点,反图上用SPFA,保存一个f[x],与d类似。

最后枚举每个节点x,用f[x]-d[x]更新答案。

#include<bits/stdc++.h>
using namespace std;
const int N=100002;
vector<int> q1[N],q2[N];
int w[N],a[N],b[N];
queue<int> q;    
int n,m;
inline int read()
{
    int ans=0,f=1;
    char ch=getchar();
    while(!isdigit(ch)) f*=(ch==-)? -1:1,ch=getchar();
    do ans=(ans<<1)+(ans<<3)+(ch^48),ch=getchar();
    while(isdigit(ch));
    return ans*f;
}
void spfa1()
{
    q.push(1);
    memset(a,127,sizeof(a));
    while(!q.empty())
    {
        int t=q.front();
        q.pop();
        a[t]=min(a[t],w[t]);
        int l=q1[t].size();
        for(int i=0;i<l;i++)
        {
            if(a[t]<a[q1[t][i]])
            {
                a[q1[t][i]]=a[t];
                q.push(q1[t][i]);
            }
        }
    }
}
void spfa2()
{
    q.push(n);
    while(!q.empty())
    {
        int t=q.front();
        q.pop();
        b[t]=max(b[t],w[t]);
        int l=q2[t].size();
        for(int i=0;i<l;i++)
        {
            if(b[t]>b[q2[t][i]])
            {
                b[q2[t][i]]=b[t];
                q.push(q2[t][i]);
            }
        }
    }
}
int main()
{
    n=read();m=read();
    for(int i=1;i<=n;i++)
    {
        w[i]=read();
    }
    for(int i=1;i<=m;i++)
    {
        int x,y,z;
        x=read();y=read();z=read();
        q1[x].push_back(y);
        q2[y].push_back(x);
        if(z==2)
        {
            q1[y].push_back(x);
            q2[x].push_back(y);
        }
    }
    spfa1();
    spfa2();
    int ans=0;
    for(int i=0;i<=n;i++)
    ans=max(ans,b[i]-a[i]);
    printf("%d",ans);
    return 0;
}

我昨晚忘记发了QAQ……真的

轻罚。

技术图片

 

以上是关于最短路问题的主要内容,如果未能解决你的问题,请参考以下文章

快看!一条进入公益传播的最短路径就在眼前

(王道408考研数据结构)第六章图-第四节3:最短路径之BFS算法(思想代码演示答题规范)

图论----最短路问题

最短路算法

最短路径问题-Dijkstra(基于图的ADT)

进入公益传播的最短路径就在眼前|「蜂群计划」全国等你