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\n");
        else printf("%d\n",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\n",ans);
        else printf("averyboynb\n");
    }
    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\n",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\n");
        else printf("%d\n",ans);
    }
    return 0;
}
View Code

 

posted @ 2018-07-27 20:40  raincle  阅读(396)  评论(0编辑  收藏  举报