zoukankan      html  css  js  c++  java
  • 7.27 图论 存图 前向星 最短路 dijstra算法 SPFA算法

    存图

    1.vector(操作简单,浪费空间)

    2.前向星:最高效的存图方式

    链式结构,结构体保存边

    const int maxn=1e5+10;//注意这里的maxn应该开题目给的双倍,因为边是双向的,加边还要开更多
    const int inf=1e9;
    int n,m;
    int head[maxn];  //head[i]表示以i为起点的最后一条边的编号;
    struct edge
    {
        int to;//这条边的终点
        int w;  //权重(边长)
        int last;  //与自己起点相同的上一条边的编号
    }Edge[maxn];//边数组
    int cnt; //记录当前边的编号
    void add(int u,int v,int w)//加边    //起点u,终点e,权重w;
    {
        Edge[cnt].to=v;
        Edge[cnt].w=w;
        Edge[cnt].last=head[u];//现在是要把编号为cnt的边加进来,
        head[u]=cnt++;//现在cnt边加进来了,cnt边为以u为起点的最后一条边
    }
    View Code

     图的遍历

    for(int j=head[i];j!=-1;j=Edge[j].last)
    {
        int v=Edge[j].to;
    }

    最短路

    Dijstra算法:适用于求边权为正,由单个源点出发到其他所有点的最短路

    //Dijkstra
    int dis[maxn];
    struct node
    {
        int u;//编号
        int d;//距离
        bool operator<(const node &res)const{return d>res.d;};//从小到大排序
        node(int uu,int dd):u(uu),d(dd){}
    };
    void dijstra(int s)//求图中其它点到源点s的最短路,求出结果存放在dis数组中
    {
        priority_queue<node> q; //优先队列
        while(!q.empty())q.pop();
        for(int i=1;i<=n;i++)
        {
            dis[i]=inf;//初始化所有距离都无限大
        }
        dis[s]=0;//s到自己本身的距离为0
        q.push(node(s,0));
        while(!q.empty())
        {
            node nx=q.top();
            q.pop();
            int u=nx.u;
            for(int i=head[u];i!=-1;i=Edge[i].last)
            {
                int to=Edge[i].to;
                int w=Edge[i].w;
                if(dis[u]+w<dis[to])
                {
                    dis[to]=dis[u]+w;
                    q.push(node(to,dis[to]));
                }
            }
        }
        return;
    }

    SPFA算法:可以处理负边,不能有负的双向边,一定会存在负环,那样子就没有最小的了,因为可以到无穷小(可以有正的双向边)

    //SPFA
    long long dis[maxn];//dis[i]为源点到i的最短路径长度
    int in[maxn];  //in[i]表示i点入队次数
    bool vis[maxn];// vis[i]表示i点是否在队列中
    int spfa(int s)//用spfa求解源点s到其他点的最短路径,结果放在dis中,存在负环,返回1,不存在返回-1
    {
        queue<int> q;
        memset(vis,false,sizeof vis);
        memset(in,0,sizeof in);
        for(int i=1;i<=n;i++)
        {
            dis[i]=inf;
        }
        dis[s]=0;
        q.push(s);
        vis[s]=true;
        in[s]=1;//顶点入队vis要做标记,另外要统计顶点的入队次数
        while(!q.empty())
        {
            int u=q.front();
            q.pop();
            vis[u]=false;//u(队头元素)出队,不在队列中
            for(int i=head[u];i!=-1;i=Edge[i].last)
            {
                int to=Edge[i].to;
                int w=Edge[i].w;
                if(dis[u]+w<dis[to])
                {
                    dis[to]=dis[u]+w;//松弛
                    if(!vis[to])//如果to点不在队列中
                    {
                        vis[to]=true;//让to在队列中
                        in[to]++;//to的入队次数+1
                        q.push(to);//让to入队
                        if(in[to]>=n)return 1;//如果这个点的入队次数超过次数上限(所有点的个数),则存在负环,返回1
                    }
    
                }
            }
        }
        return -1;//不存在负环
    }

    A题:

    averyboy现在在实习。每天早上他要步行去公司上班,你肯定知道,他是一个非常男孩,所以他会选择最短的路去公司。现在给你averyboy到公司途中的若干个站点,标号为1~N,averyboy的开始在1号站点,它的公司在N号站点,然后给你若干条边代表站点有路可以通过(可能会有重边)。现在你需要告诉averyboy他到公司的最短路径是多少。

    模板题:

    #include<bits/stdc++.h>
    using namespace std;
    const int maxn=1e5+10;//注意这里的maxn应该开题目给的双倍,因为边是双向的,加边还要开更多
    const int inf=1e9;
    int n,m;
    int head[maxn];  //head[i]表示以i为起点的最后一条边的编号;
    struct edge
    {
        int to;//这条边的终点
        int w;  //权重(边长)
        int last;  //与自己起点相同的上一条边的编号
    }Edge[maxn];//边数组
    int cnt; //记录当前已有的边数
    void add(int u,int v,int w)//加边    //起点u,终点e,权重w;
    {
        //cout<<1<<endl;
        Edge[cnt].to=v;
        Edge[cnt].w=w;
        Edge[cnt].last=head[u];//现在是要把编号为cnt的边加进来,
        head[u]=cnt++;//现在cnt边加进来了,cnt边为以u为起点的最后一条边
    }
    //Dijkstra
    int dis[maxn];
    struct node
    {
        int u;//编号
        int d;//距离
        bool operator<(const node &res)const{return d>res.d;};//从小到大排序
        node(int uu,int dd):u(uu),d(dd){}
    };
    void dijstra(int s)//求图中其它点到源点s的最短路,求出结果存放在dis数组中
    {
        priority_queue<node> q;
        while(!q.empty())q.pop();
        for(int i=1;i<=n;i++)
        {
            dis[i]=inf;//初始化所有距离都无限大
        }
        dis[s]=0;//s到自己本身的距离为0
        q.push(node(s,0));
        while(!q.empty())
        {
            node nx=q.top();
            q.pop();
            int u=nx.u;
            for(int i=head[u];i!=-1;i=Edge[i].last)
            {
                int to=Edge[i].to;
                int w=Edge[i].w;
                if(dis[u]+w<dis[to])
                {
                    dis[to]=dis[u]+w;
                    q.push(node(to,dis[to]));
                    //cout<<1<<endl;
                }
            }
        }
        return;
    }
    int main()
    {
        int t;
        cin>>t;
        while(t--)
        {
            int i;
            scanf("%d%d",&n,&m);
            for(i=1;i<=n;i++)head[i]=-1;
            cnt=1;
            for(i=1;i<=m;i++)
            {
                int u,v,w;
                scanf("%d%d%d",&u,&v,&w);
                add(u,v,w);
                add(v,u,w);
            }
            dijstra(1);
            int ans=dis[n];
            if(ans==inf)printf("averyboynb
    ");
            else printf("%d
    ",ans);
        }
        return 0;
    }
    View Code

    B题:

    averyboy现在在实习。每天早上他要步行去公司上班,你肯定知道,他是一个非常男孩,所以他会选择最短的路去公司。现在给你averyboy到公司途中的若干个站点,标号为1~N,现在averyboy的起点可以是多个点,averyboy的终点也就是公司也可以是多个点,给你站点之间的边和它们的权值。现在你需要告诉averyboy他到公司的最短路径是多少(只需从任意一个起点开始到达任意一个终点就行)。
     

    Input

    第一行一个整数T(T <= 5)代表测试数据的组数
    接下来T组测试数据。
    每组测试数据第一行为两个整数N,M,k1, k2(1 <= N <= 1000, 0 <= M <= 10000)代表站点的个数和边的条数以及起点的个数,终点的个数(1 <= k1, k2 <= N)
    接下来一行k1个数x[i],代表averyboy起点(1 <= x[i] <= N)
    接下来一行k2个数y[i],代表终点(1 <= y[i] <= N)
    接下来M行,每一行三个数u, v, w代表站点u,v之间有一条无向边(可能会有重边),边的权值为w(1 <= u, v <= N, 0 <= w <= 1000)

    解题思路:

    建立一个超级源点s,与起点集合的所有点连边,权值为0,建立一个汇点t,与所有终点集合的点连边,权值为0,对s做dijstra,求dis[t]即可。

    #include <bits/stdc++.h>
    using namespace std;
    const int maxn=1e5+10;
    const int inf=(int)0x3f3f3f3f;
    int n,m,k1,k2;
    int x[1100];
    int y[1100];
    int head[1100];  //head[i]表示以i为起点的最后一条边的编号;
    struct edge
    {
        int to;//这条边的终点
        int w;  //权重(边长)
        int last;  //与自己起点相同的上一条边的编号
    }Edge[maxn];//边数组
    int cnt; //记录当前已有的边数
    void add(int u,int v,int w)//加边    //起点u,终点v,权重w;
    {
        //cout<<1<<endl;
        Edge[cnt].to=v;
        Edge[cnt].w=w;
        Edge[cnt].last=head[u];//现在是要把编号为cnt的边加进来,
        head[u]=cnt++;//现在cnt边加进来了,cnt边为以u为起点的最后一条边
    }
    //Dijkstra
    long long dis[maxn];
    struct node
    {
        int u;//编号
        int d;//距离
        bool operator<(const node &res)const{return d>res.d;};//从小到大排序
        node(int uu,int dd):u(uu),d(dd){}
    };
    void dijstra(int s)//求图中其它点到源点s的最短路,求出结果存放在dis数组中
    {
        priority_queue<node> q;
        while(!q.empty())q.pop();
        for(int i=0;i<=n+1;i++)
        {
            dis[i]=inf;//初始化所有距离都无限大
        }
        dis[s]=0;//s到自己本身的距离为0
        q.push(node(s,0));
        while(!q.empty())
        {
            node nx=q.top();
            q.pop();
            int u=nx.u;
            for(int i=head[u];i!=-1;i=Edge[i].last)
            {
                //cout<<1<<endl;
                int to=Edge[i].to;
                int w=Edge[i].w;
                if(dis[u]+w<dis[to])
                {
                    dis[to]=dis[u]+w;
                    q.push(node(to,dis[to]));
                    //cout<<1<<endl;
                }
            }
        }
        return;
    }
    int main()
    {
        int t;
        cin>>t;
        while(t--)
        {
            scanf("%d%d%d%d",&n,&m,&k1,&k2);
            cnt=1;
            int i;
            for(i=0;i<=n+1;i++)head[i]=-1;
            int s=0,t=n+1;
            for(i=1;i<=k1;i++)
            {
                scanf("%d",&x[i]);
                add(s,x[i],0);
                add(x[i],s,0);
            }
            for(i=1;i<=k2;i++)
            {
                scanf("%d",&y[i]);
                add(y[i],t,0);
                add(t,y[i],0);
            }
            for(i=1;i<=m;i++)
            {
                int u,v,w;
                scanf("%d%d%d",&u,&v,&w);
                add(u,v,w);
                add(v,u,w);
            }
            dijstra(s);
            int ans=dis[t];
            if(ans!=inf)printf("%d
    ",ans);
            else printf("averyboynb
    ");
        }
        return 0;
    }
    View Code

    C题:

    averyboy最近想买一个新的mac,所以他想赚点钱。所以他选择去卖书。现在有N个城市,书在每一个城市价格不一样,但是在同一个城市,买一本书和卖一本书的价格一样,然后如果城市x,y之间有一条权值为w的边,averyboy从城市x到y需要支付w费用,现在给你书在N个城市的价格和城市之间的边以及权值(N - 1条边,刚好使N个城市想连通),averyboy需要选择一个城市为起点,买一本书,然后跑到另外一个城市将这本书卖掉。averyboy数学不太好,你能告诉他他最多能赚多少钱吗?

    Input

    第一行一个整数T(T <= 5)代表测试数据的组数
    接下来T组测试数据
    每组测试数据第一行为一个正整数N(N <= 1e5)代表城市的个数
    接下来一行N个整数a[i],代表书在每个城市的价格(1 <= a[i] <= 10000)
    接下来N - 1行,每行三个数u, v, w(1 <= u, v <= N, 1 <= w <= 1000)代表城市u,v之间有一条权值为w的边

    Output

    对于每组测试数据,输出一个整数,表示averyboy能赚到的最多的钱。

    Sample Input

    1  
    4  
    10 40 15 30  
    1 2 30
    1 3 2
    3 4 10

    Sample Output

    8

    HINT

    他选择从1号城市买书,到4号城市卖书,然后他买书和路费一共花费10 + 2 + 10 = 22,到了4号城市把书卖掉,赚30元,所以最终赚了30 - 22 = 8元,这种情况下他能赚的最多。

    解题思路:边权分离  源点汇点

    建立一个超级源点s,与所有点连边,边的权值即等于连线点的书的价值,建立一个汇点t,与所有点连边,边的权值等于负的连线点的书的价值

    因为有负的边权,所以用SPFA,对s做SPFA求dis[t]即可

    求出来的t是最小的(买书钱+路费-卖书钱),它的负数即是最大的(卖书钱-路费-买书钱)

    #include <bits/stdc++.h>
    using namespace std;
    const int maxn=1e6+10;
    const int inf=1e9;
    int n;
    int head[maxn];  //head[i]表示以i为起点的最后一条边的编号; //head[i]表示以i为起点的最后一条边的编号;
    struct edge
    {
        int to;//这条边的终点
        int w;  //权重(边长)
        int last;  //与自己起点相同的上一条边的编号
    }Edge[maxn];//边数组
    int cnt; //记录当前已有的边数
    void add(int u,int v,int w)//加边    //起点u,终点e,权重w;
    {
        Edge[cnt].to=v;
        Edge[cnt].w=w;
        Edge[cnt].last=head[u];//现在是要把编号为cnt的边加进来,
        head[u]=cnt++;//现在cnt边加进来了,cnt边为以u为起点的最后一条边
    }
    //SPFA
    long long dis[maxn];//dis[i]为源点到i的最短路径长度
    int in[maxn];  //in[i]表示i点入队次数
    bool vis[maxn];// vis[i]表示i点是否在队列中
    int spfa(int s)//用spfa求解源点s到其他点的最短路径,结果放在dis中,存在负环,返回1,不存在返回-1
    {
        queue<int> q;
        memset(vis,false,sizeof vis);
        memset(in,0,sizeof in);
        for(int i=0;i<=n+1;i++)
        {
            dis[i]=inf;
        }
        dis[s]=0;
        q.push(s);
        vis[s]=true;
        in[s]=1;//顶点入队vis要做标记,另外要统计顶点的入队次数
        while(!q.empty())
        {
            int u=q.front();
            q.pop();
            vis[u]=false;//u(队头元素)出队,不在队列中
            for(int i=head[u];i!=-1;i=Edge[i].last)
            {
                int to=Edge[i].to;
                int w=Edge[i].w;
                if(dis[u]+w<dis[to])
                {
                    dis[to]=dis[u]+w;//松弛
                    if(!vis[to])//如果to点不在队列中
                    {
                        vis[to]=true;//让to在队列中
                        in[to]++;//to的入队次数+1
                        q.push(to);//让to入队
                        if(in[to]>=n)return 1;//如果这个点的入队次数超过次数上限(所有点的个数),则存在负环,返回1
                    }
    
                }
            }
        }
        return -1;//不存在负环
    }
    int book[maxn];
    int main()
    {
       int t;
       cin>>t;
       while(t--)
        {
            scanf("%d",&n);
            int i;
            for(i=0;i<=n+1;i++)head[i]=-1;
            cnt=1;
            int s=0,t=n+1;
            for(i=1;i<=n;i++)
            {
                scanf("%d",&book[i]);
                add(s,i,book[i]);
                add(i,t,-book[i]);
            }
            for(i=1;i<=n-1;i++)
            {
                int u,v,w;
                scanf("%d%d%d",&u,&v,&w);
                add(u,v,w);
                add(v,u,w);
            }
            int b=spfa(s);
            long long ans=-dis[t];
            printf("%lld
    ",ans);
        }
        return 0;
    }
    View Code

    D题:

    Description

    averyboy不仅是一个非常男孩,他还是一位老司机。现在averyboy在开火车,一共有N个火车站,每个火车站出站口只有若干个出口,这些出口分别对应一些其他的火车站,代表如果从这一个出口开出火车,下一站将会达到该出口对应的火车站。每一个火车站有一个默认的出口,如果此次averyboy想要出站的出口不是默认出口,他将会被他的上级批评一次。现在averyboy需要从A站到B站,给你每一个火车站出站口的出口的情况,你需要告诉averyboy他最少要被批评多少次

    Input

    第一行一个整数T(T <= 5)代表测试数据的组数
    接下来T组测试数据
    每组测试数据的第一行三个整数N, A, B(1 <= N <= 100, 1 <= A, B <= N)分别代表火车站的数量以及averyboy的起点站和终点站
    接下来N行数据,第i行第一个数为k,代表第i个火车站有k个出口,后面k个整数(k个整数可能会有若干个相同),代表每个出口通向的下一个火车站编号,k个数中的第一个表示这个火车站默认的出口。(0 <= k <= N)

    Output

    对于每组测试数据,如果A能够达到B,输出一个整数,代表averyboy最小被批评的次数反之输出averyboynb

    Sample Input

    2
    3 2 1
    2 2 3
    2 3 1
    2 1 2
    3 1 2
    2 3 2
    1 3
    1 1
    

    Sample Output

    0
    1

    HINT

    理解题意:即是说若与火车站相连的出口是默认出口,则不会受领导批评,即边权为0,若不是默认出口,会受到批评,边权为1,求最短路即可。(权值可以有距离,价值等多重意义)

    #include <bits/stdc++.h>
    using namespace std;
    const int maxn=1e5+10;
    const int inf=1e9;
    int n;
    int head[maxn];  //head[i]表示以i为起点的最后一条边的编号;
    struct edge
    {
        int to;//这条边的终点
        int w;  //权重(边长)
        int last;  //与自己起点相同的上一条边的编号
    }Edge[maxn];//边数组
    int cnt; //记录当前已有的边数
    void add(int u,int v,int w)//加边    //起点u,终点v,权重w;
    {
        Edge[cnt].to=v;
        Edge[cnt].w=w;
        Edge[cnt].last=head[u];//现在是要把编号为cnt的边加进来,
        head[u]=cnt++;//现在cnt边加进来了,cnt边为以u为起点的最后一条边
    }
    //Dijkstra
    int dis[maxn];
    struct node
    {
        int u;//编号
        int d;//距离
        bool operator<(const node &res)const{return d>res.d;};//从小到大排序
        node(int uu,int dd):u(uu),d(dd){}
    };
    void dijstra(int s)//求图中其它点到源点s的最短路,求出结果存放在dis数组中
    {
        priority_queue<node> q;
        while(!q.empty())q.pop();
        for(int i=1;i<=n;i++)
        {
            dis[i]=inf;//初始化所有距离都无限大
        }
        dis[s]=0;//s到自己本身的距离为0
        q.push(node(s,0));
        while(!q.empty())
        {
            node nx=q.top();
            q.pop();
            int u=nx.u;
            for(int i=head[u];i!=-1;i=Edge[i].last)
            {
                int to=Edge[i].to;
                int w=Edge[i].w;
                if(dis[u]+w<dis[to])
                {
                    dis[to]=dis[u]+w;
                    q.push(node(to,dis[to]));
                    //cout<<1<<endl;
                }
            }
        }
        return;
    }
    int main()
    {
        int t;
        cin>>t;
        while(t--)
        {
            int a,b;
            scanf("%d%d%d",&n,&a,&b);
            int i,j;
            for(i=1;i<=n;i++)
            {
                head[i]=-1;
            }
            cnt=1;
            for(i=1;i<=n;i++)
            {
                int k;
                scanf("%d",&k);
                for(j=1;j<=k;j++)
                {
                    int v;
                    scanf("%d",&v);
                    if(j==1)add(i,v,0);
                    else add(i,v,1);
                }
            }
            dijstra(a);
            int ans=dis[b];
            if(ans==inf)printf("averyboynb
    ");
            else printf("%d
    ",ans);
        }
        return 0;
    }
    View Code
  • 相关阅读:
    [笔记].怎样给μC/OSII的任务传递参数
    [原创].怎样在WPS上实现代码语法高亮
    [笔记].Nios II 9.1的sys/alt_irq.h与之前版本的区别
    [原创].使用Nios II 9.1中的Flash Programmer无法固化程序到EPCS上
    [笔记].浅析在Nios II中的两种寄存器映射方法的异同
    [原创].怎样在Nios II上跑μC/OSII
    [转载].基于Nios II的DMA传输
    [转载].SSRAM、SDRAM和Flash简要介绍
    [笔记].一种独立键盘消抖的Verilog写法
    [笔记].I2C札记
  • 原文地址:https://www.cnblogs.com/raincle/p/9379563.html
Copyright © 2011-2022 走看看