第三关 ——图论:最短路

14:52:09 今天天气好晴朗,处处好风光。

阳光犹如希望,我即将要回去打游戏的希望,希望那么那么大,那么那么刺眼!!!

好的,接下来为您播报考试后续情况:不算太差,还看得过去,至少,应该能活着把年过过去。接下来,就介绍一下在父母看到成绩后想要打你时如何通过最短路逃跑

目录

  • Dijkstra算法

  • Bellman-Ford算法

  • SPFA算法

  • Floyd算法

首先,要来简单论述一下“最短路”这个问题。要求最短路,无论什么方法,都要先存图。

百“路”图为先

那么,图分为两种,有向图与无向图。无论是有向图还是无向图,存图方式都是一样的。分为两种:

  • 邻接矩阵(空间复杂度:O(n2))

可以说,矩阵存图是非常非常的简单易打了。话不多说,放代码

int n,m;
    cin>>n>>m;
    memset(e,127,sizeof(e));//将矩阵中所有边初始化为无穷大
    for(int i=1; i<=n; i++) e[i][i]=0;
    for(int i=1; i<=m; i++) {
        int x,y,z;
        cin>>x>>y>>z;//x点与y点有一条边,边的权值为z
        e[x][y]=z;
        e[y][x]=z;
    }
  • 邻接表(空间复杂度:O(n+m))

邻接表可以用数组模拟链表指针:

void add(int x,int y,int z)//x点与y点有一条边,边的权值为z
{
    ver[++tot]=y,edge[tot]=z;
    next[tot]=head[x],head[x]=tot;
}
for(int i=head[x];i;i=next[i])
{
    int y=ver[i],z=edge[i];
}//于主函数中

也可以用vector存储:

vector<int>e[1000];//vector代替邻接表
for(int i=1;i<=m;i++)
{
    int x,y,z;
    scanf("%d%d",&x,&y);//x,y两点间有一条边
    e[x].push_back(y);//加入x的vector
}

好的,接下来开始讲我们的单源最短路径的几种算法(单源最短路径:源点到每个点的距离)

  • Dijkstra算法(基于贪心思想,适用于没有负权的图)

算法流程

  1. 初始化dis[1]=0,其余dis数值为正无穷大
  2. 找到未被标记的最小节点x,并标记
  3. 扫描x的所有出边,若dis[y]>dis[x]+z(边权),则用dis[x]+z更新dis[y]的值
  4. 直到节点被标记完为止

dis[x]代表源点到x的最短路径。

它的时间复杂度为(O(logn))

单源最短路问题

题目描述

输入一个无向网络,输出其中2个顶点之间的最短路径长度

输入

输入文件第一行为n和m,表示有n个顶点和m条带权边,其中顶点编号是从1到n,接下来有m行,每行三个整数,分别表示两个顶点编号和对应边的权值,再接下来有一行,两个整数表示要求的最短路径的两个顶点编号

输出

输出文件就一行,即两个顶点间的最短路径长度(权值和)

输入样例

4 5
1 2 2
1 3 1
2 3 2
2 4 1
3 4 6
1 4

输出样例

3

这里需要用vis数组存储是否标记。

#include<bits/stdc++.h>
using namespace std;
int e[1000][1000],dis[1000],vis[1000];
int main() {
    int n,m;
    cin>>n>>m;
    memset(e,127,sizeof(e));
    for(int i=1; i<=n; i++) e[i][i]=0;
    for(int i=1; i<=m; i++) {
        int x,y,z;
        cin>>x>>y>>z;
        e[x][y]=z;
        e[y][x]=z;
    }
    int s,b;
    cin>>s>>b;
    for(int i=1; i<=n; i++) {
        dis[i]=e[s][i];
    }
    vis[s]=1;
    for(int i=1; i<=n; i++) {
        int mmin=99999,k;
        for(int j=1; j<=n; j++) {
            if(vis[j]!=1) {
                if(dis[j]<mmin) {
                    mmin=dis[j];
                    k=j;
                }
            }
        }
        vis[k]=1;
        for(int j=1; j<=n; j++) {
            dis[j]=min(dis[j],dis[k]+e[k][j]);
        }
    }
    cout<<dis[b];
    return 0;
}

Dijkstra堆优化(时间复杂度(O(n2)))

堆排序

#include<iostream>
#include<queue>
using namespace std;
priority_queue<int, vector<int>, greater<int> >q;
int main(){
    int n,x;
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>x;
        q.push(x);
    }
    while(!q.empty()){
        cout<<q.top()<<" ";
        q.pop();
    }
    return 0;
} 

https://www.luogu.com.cn/problem/P4779

所以就有了堆优化的Dijkstra

堆中每个元素存两个值 结点编号xx和入堆是该点被更新成的距离disdis
disdis为第一关键字(即按dis的小根堆) 主要思想不变 每次找距离最小点是直接取出堆顶并删除堆顶
更新最短路的时候 将更新后的节点入堆

注意一个点可能会被更新多次而入堆多次 但是只有最后一次入堆才是正确的dis 同时也一定是在所有入堆操作结束后才会出堆 所以直接开个数组判断有没有出堆过就好了 当然也可以根据堆中元素的距离大小和点的最短路大小直接判断

#include<bits/stdc++.h>
#define M(x,y) make_pair(x,y)
using namespace std;
int fr[100010],to[200010],nex[200010],v[200010],tl,d[100010];
bool b[100010];
void add(int x,int y,int w){
    to[++tl]=y;
    v[tl]=w;
    nex[tl]=fr[x];
    fr[x]=tl;
}
priority_queue< pair<int,int> > q;
int main(){
    int n,m,x,y,z,s;
    scanf("%d%d%d",&n,&m,&s);
    for(int i=1;i<=m;i++){
        scanf("%d%d%d",&x,&y,&z);
        add(x,y,z);
    }
    for(int i=1;i<=n;i++) d[i]=1e10;
    d[s]=0;
    q.push(M(0,s));
    while(!q.empty()){
        int x=q.top().second;
        q.pop();
        if(b[x]) continue;
        b[x]=1;
        for(int i=fr[x];i;i=nex[i]){
            int y=to[i],l=v[i];
            if(d[y]>d[x]+l){
                d[y]=d[x]+l;
                q.push(M(-d[y],y));
            }
        }
    }
    for(int i=1;i<=n;i++) printf("%d ",d[i]);
    return 0;
}
  • Bellman-Ford算法和SPFA算法

Bellman-Ford算法

算法流程:

  1. 扫描所有边(x,y,z),若dis[y]>dis[x]+z(边权),则用dis[x]+z更新dis[y]的值
  2. 重复直到没有更新
  3. 时间复杂度(O(nm))

SPFA算法

算法流程:

  1. 建立一个队列,最初队列只含起点1
  2. 取队头结点x,扫描它的所有出边,若dis[y]>dis[x]+z(边权),则用dis[x]+z更新dis[y]的值
  3. 若y不在队列中的话,将y入队
  4. 重复直到队列为空
  5. 时间复杂度(O(km))
  6. 可解决负权,也可判断有无负环

可惜的是,该算法已死。

http://noi-test.zzstep.com/contest/0x60%E3%80%8C%E5%9B%BE%E8%AE%BA%E3%80%8D%E4%BE%8B%E9%A2%98/6101%20%E6%9C%80%E4%BC%98%E8%B4%B8%E6%98%93

以1为起点,在原图上使用SPFA算法,用数组d[x]表示在原图中从节点1到节点x的所有路径中,能够经过的权值最小的节点的权值

以n为起点,在反图上使用SPFA算法,用数组f[x]表示在原图中从节点x到节点n的所有路径中(反图中是n到x),能够经过的权值最大的节点的权值

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

#include<bits/stdc++.h>
using namespace std;
const int N=100002;
int a[N],b[N],c[N];
vector<int> g[N];
vector<int> s[N];
queue<int> q;
int main()
{
    int n,m;
    cin>>n>>m;
    for(int i=1;i<=n;++i)
    cin>>a[i];
    for(int i=1;i<=m;i++)
    {
        int x,y,z;
        cin>>x>>y>>z;
        g[x].push_back(y);
        s[y].push_back(x);
        if(z==2)
        {
            g[y].push_back(x);
            s[x].push_back(y);
        }
    }
    q.push(1);
    memset(c,60,sizeof(c));
    c[1]=9999999;
    while(!q.empty())
    {
        int t=q.front();
        q.pop();
        c[t]=min(c[t],a[t]);
        for(int i=0;i<g[t].size();i++)
        if(c[t]<c[g[t][i]])
        c[g[t][i]]=c[t],q.push(g[t][i]);
    }
    q.push(n);
    while(!q.empty())
    {
        int t=q.front();
        q.pop();
        b[t]=max(b[t],a[t]);
        for(int i=0;i<s[t].size();i++)
        if(b[t]>b[s[t][i]])
        b[s[t][i]]=b[t],q.push(s[t][i]);
    }
    int ans=0;
    for(int i=1;i<=n;i++)
    ans=max(ans,b[i]-c[i]);
    cout<<ans;
    return 0;
}

20:12:48 所以暂时将你眼睛闭了起来   ——伍佰

多源最短路径(任意两点间最短路径)

  • Floyd算法

状态转移方程:d[i][j]=min(d[i][j],d[i][k]+d[k][j]);

可用邻接矩阵存图

每队顶点之间的最短路

题目描述

输入一个有向图的邻接矩阵格式,输出每对顶点间的最短路径长度

输入

输入第一行为n,表示下面是个n n的矩阵,接下来就是n n的矩阵形式,每个元素值都是整型,如果不能直接到,则是-1

输出

输出也是一个n * n的矩阵形式,每个元素的值为两个顶点间的最短路径值,如果到不了,则输出-1,最后一行的换行也输出

输入样例

3
0 1 3
2 0 5
3 2 0

输出样例

0 1 3
2 0 5
3 2 0这是一道模板题,所以直接看代码。
#include<bits/stdc++.h>
using namespace std;
int a[60][60];
int x,y,n;
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
    for(int j=1;j<=n;j++)
    {
        cin>>a[i][j];
        if(a[i][j]==-1)
        a[i][j]=9999999;
    }

    for(int k=1;k<=n;k++)
    for(int i=1;i<=n;i++)
    for(int j=1;j<=n;j++)
    a[i][j]=min(a[i][j],a[i][k]+a[k][j]);
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=n;j++)
        {
            if(a[i][j]==9999999)
            a[i][j]=-1;
            cout<<a[i][j]<<" ";
        }
        cout<<endl;
    }
    return 0;
}

文化之旅

因为Floyd是插点找最短路,所以每次状态都会被记录,插的点的文化就会被记录在这条路里,比如若在i,j中插入k时没有矛盾且满足最短路,那么将两条路上的信息合并并更新k的信息,就可以得到新路径的值。只需要用Floyd再加上一部分优化就好,只是要想到用Floyd不容易,可能会想到用Dijkstra算法(可是不知道能不能过)

#include<bits/stdc++.h>
using namespace std;
int n,k,m,s,t,u,v,d,c[102],a[102][102],f[102][102];
int main()
{
    cin>>n>>k>>m>>s>>t;
    for(int i=1;i<=n;i++)
    cin>>c[i];
    for(int i=1;i<=k;i++)
    for(int j=1;j<=k;j++)
    cin>>a[i][j];
    memset(f,60,sizeof(f));
    for(int i=1;i<=m;i++)
    {
        cin>>u>>v>>d;
        if(!a[c[v]][c[u]]&&c[v]!=c[u])f[u][v]=min(f[u][v],d);
        if(!a[c[u]][c[v]]&&c[u]!=c[v])f[v][u]=min(f[v][u],d);
    }
    if(c[s]==c[t])
    {
        cout<<-1;
        return 0;
    }
    for(int i=1;i<=n;i++)
    for(int j=1;j<=n;j++)
    {
        if(i!=j)
        for(int k=1;k<=n;k++)
        {
            if(j!=k&&i!=k)
            f[i][j]=min(f[i][j],f[i][k]+f[k][j]);
        }
    }
    if(f[s][t]>999999)cout<<-1;
    else cout<<f[s][t];
    return 0;
 } 

20:52:02

我终于要认认真真看电视了!!!

原文地址:https://www.cnblogs.com/wybxz/p/12214575.html

时间: 2024-11-09 03:56:52

第三关 ——图论:最短路的相关文章

bzoj4144 [AMPPZ2014]Petrol 图论 最短路 并查集

bzoj4144 [AMPPZ2014]Petrol 图论 最短路 并查集 1.这道题我们主要就是要求出距离一个油站的最近的油站 首先我们dijkstra 求出任意一个点到 离他最近的油站的距离 2.然后会发现 如果一条边的两个端点 的最近油站不同的话 那么这条边就会在这两个油站的最短路上 3.然后对于每一条边 我们将他的权值 变为 dis[ u ] + dis[ v ] + e[ i ][ j ] 如果u与v最近油站相同 那么这个无意义 如果不同 那么久表示 u 最近油站 到 v 最近油站的最

POJ 3259 Wormholes (图论---最短路 Bellman-Ford || SPFA)

链接:http://poj.org/problem?id=3259 Description While exploring his many farms, Farmer John has discovered a number of amazing wormholes. A wormhole is very peculiar because it is a one-way path that delivers you to its destination at a time that is BE

HDU 5521 [图论][最短路][建图灵感]

/* 思前想后 还是决定坚持写博客吧... 题意: n个点,m个集合.每个集合里边的点是联通的且任意两点之间有一条dis[i]的边(每个集合一个dis[i]) 求同时从第1个点和第n个点出发的两个人相遇的最短时间,并输出相遇的地点,如果有多个按编号大小顺序输出. 输入: 测试数据 t n m 以下m行每行 dis[i] 该集合点的数量 ...每个点的标号 数据范围: n 2-1e5 所有集合的元素的数量和 1e6 思路: 如果直接跑最短路,边会存爆的. 考虑换种思路建边. 每个集合看作两个点,一

图论 最短路 基础

图论基础 , 最短路 图的简单概念 顶点 (Vertex), 边 (Edge) 有向图 , 无向图 , 无向图是一种特殊的有向图 度,有向图分为出度 和 入度,无向图的度,代表 连出去的边 顶点和边都可以具有属性,称为权重,顶点称为 点权,边 称为 边权 稠密图 边很多,大约是 顶点的平方 稀疏图 边很少 , 重边(平行边),自环, 路径:从一给顶点到达另一个顶点称为一条路径 路径中边的数量称为路径长度,如果路径中的顶点均不重复,称为简单路径 如果路径中的第一个顶点 \(v_i\) 和最后一个顶

洛谷P1027 Car的旅行路线 计算几何 图论最短路

题意 求某城到某城的最小花费 一个城中有四个机场,一个城中的机场相互可达,用公路到达,但是不同城的公路的单位路程的费不同,两个不同城的机场(我不知道相同城可不可以)可以通过机场到达,且飞机单位路程价格一定,问从 a 城到b城的最小花费,可从a的任一机场出发,从 b 的任一机场结束 . 题解 这道题思路还算容易,就是求最短路,只是建图比较麻烦, 总体思路 1.建图(1) 相同城 的四个机场两两连线 求距离, [1]但是他只给出了三个点,也就是说这第四个点要我们自己求 首先他给出三个点,这三个点一定

图论——最短路

Tyvj 1221 微子危机——战略 背景 №.3Summer联盟战前兵力战略转移. 描述 Summer的兵力分布在各个星球上,现在需要把他们全部转移到某个星球上.Summer一共拥有N个星球(1-N),你要把这N个星球上的兵力转到第M个星球上.本来每个星球之间都有星际轨道连接,但Guiolk监视了某些轨道,我们一但走上这些轨道,有可能受到他的攻击.为了安全,Summer不会走被监视的轨道.于是,只有L个星际轨道是被批准通过的.Summer的国防部想统计一下所需的最短路程(即每个星球到第M星球的

图论——最短路②

RT 最短路计数 给出一个N个顶点M条边的无向无权图,顶点编号为1-N.问从顶点1开始,到其他每个点的最短路有几条. 输入输出格式 输入格式: 输入第一行包含2个正整数N,M,为图的顶点数与边数. 接下来M行,每行两个正整数x, y,表示有一条顶点x连向顶点y的边,请注意可能有自环与重边. 输出格式: 输出包括N行,每行一个非负整数,第i行输出从顶点1到顶点i有多少条不同的最短路,由于答案有可能会很大,你只需要输出mod 100003后的结果即可.如果无法到达顶点i则输出0. 样例 输入: 输出

图论——最短路①

RT 找工就业 Bessie is running out of money and is searching for jobs. Farmer John knows this and wants the cows to travel around so he has imposed a rule that his cows can only make D (1 <= D <= 1,000) dollars in a city before they must work in another

[图论][最短路]步行

题目描述 ftiasch又开发了一个奇怪的游戏,这个游戏是这样的:有N个格子排成一列,每个格子上有一个数字,第i个格子的数字记为Ai.这个游戏有2种操作:1.如果现在在第i个格子,则可以跳到第Ai个格子.2.把某个Ai增加或减少1.nm开始在第1个格子,他需要走到第N个格子才能通关.现在他已经头昏脑涨啦,需要你帮助他求出,从起点到终点最少需要多少次操作. 输入 第1行,1个整数N.第2行,N个整数Ai. 输出 第1行,1个整数,表示最少的操作次数. 样例输入 5 3 4 2 5 3 样例输出 3