单源最短路建图

单源最短路的建图方式

  • 1.1129. 热浪
  • 2.1128.信使
  • 3.1127. 香甜的黄油
  • 4.903. 昂贵的聘礼
  • 5.1126. 最小花费
  • 6.920. 最优乘车

1.1129. 热浪

单源最短路建图_第1张图片

思路:
将问题化简一下:
求x到y的最小距离(这里花费等价于距离)

那么使用4种基本模板:

  1. dijkstra朴素做法
  2. dijkstra优化做法
  3. bellman_ford算法
  4. spaf算法(bellman_ford)算法

由于是刚开始写,所以说明每个算法的基本逻辑,但是很明显做法2和做法4是优先考虑的(因为是优化版嘛)

这里用四个方法解决这道最简单的求单源路径最短问题

1.dijstra朴素版

//dijkstra朴素版

#include
#include
using namespace std;

const int N=2510,M=6200*2+10;

int d[N];  //d[i]=j表示点i距离源点的距离是j(源点就是起点)、
bool st[N];  //记录哪些点被找过

int h[N],e[M],ne[M],w[M];   //构建邻接表
int idx;

int n,c,start,End;//n个点,c条双向边,start是起点,end是终点

void add(int a,int b,int c)
{
    e[idx]=b;  //新节点
    ne[idx]=h[a];  //新节点的下一个节点指向h[a]的下一个节点(头插法),h[a]是a的邻接表
    //比如原来:  a->c->d->null
    //现在:  a  b->c->d->null
    w[idx]=c;  //新边的权值
    h[a]=idx++;  //然后h[a]指向新节点,就完成了a->b->c->d->null(原来a的下一个节点)
}

void dijkstra()  //朴素dijkstra
{
    //每次找到距离源点最近的点x,然后以x为中心更新其他的点(距离只要比原来小就更新)
    memset(d,0x3f,sizeof d);
    d[start]=0;
    // for(int i=h[start];i!=-1;i=ne[i])
    // {
    //     int j=e[i];
    //     d[j]=w[i];
    // }
    
    for(int i=0;i<n;i++)  //每次都可以确定一个点到源点的距离最小,那么需要找n次,源点也算
    {
        int t=-1;
        for(int j=1;j<=n;j++)  //找到距离源点最近的点
        {
            if(!st[j]&&(t==-1||d[t]>d[j]))
                t=j;
        }
        
        st[t]=true;  //表示t点到源点的最短距离就此确定。
        //这一步很巧秒,用反证法证明:此时t是所有点中距离源点最近的点
        //如果t不是距离源点最近的点,那么一定有其他点经过若干边距离源点更近,
        //但是此时t已经是距离源点最近的点了,单凡t或者其他点再经过任意一个边,到达源点
        //的距离都一定大于此时的t到源点的距离(没有负边的情况)
        
        
        //接下来是通过t更新距离源点更加近的点
        for(int j=h[t];j!=-1;j=ne[j])
        {
            int k=e[j];
            if(st[k])continue;
            d[k]=min(d[k],d[t]+w[j]);
        }
    }
        
    
}

int main()
{
    
    cin>>n>>c>>start>>End;   
    
    memset(h,-1,sizeof h);  //初始化邻接表
    
    for(int i=0;i<c;i++)
    {
        int a,b,c;
        cin>>a>>b>>c;
        add(a,b,c);
        add(b,a,c);
    }
    
    dijkstra();
    cout<<d[End];
    
    return 0;
}

2.优化版dijkstra

#include
#include
#include
#include
using namespace std;

const int N=2510,M=6200*2+10;

typedef pair<int,int> PII;
int h[N],e[M],ne[M],w[M],idx;


int d[N];
bool st[N];


int n,c,ts,td;

void add(int a,int b,int c)
{
    e[idx]=b;
    ne[idx]=h[a];
    w[idx]=c;
    h[a]=idx++;
}

void dijkstra()
{
    memset(d,0x3f,sizeof d);
    d[ts]=0;
    priority_queue<PII,vector<PII>,greater<PII>>heap;   //距离源点的距离被排好序,这是优化的唯一步骤
    heap.push({0,ts}); //first是距离,second是节点编号
    while(heap.size())  
    {
        auto t=heap.top();
        heap.pop();
        int distance=t.first,ver=t.second;
        
        if(st[ver])continue;
        st[ver]=true;
        
        for(int i=h[ver];i!=-1;i=ne[i])
        {
            int j=e[i];
            if(st[j])continue;
            if(distance+w[i]<d[j])
            {
                d[j]=distance+w[i];
                heap.push({d[j],j});
            }
        }
    }
    
}

int main()
{
    cin>>n>>c>>ts>>td;
    
    memset(h,-1,sizeof h);
    while(c--)
    {
        int a,b,c;
        cin>>a>>b>>c;
        add(a,b,c);
        add(b,a,c);
    }
    
    dijkstra();
    
    cout<<d[td];
    return 0;
    
}

3.bellman_ford算法
说一句,可能不对,但是这个算法好像floyd算法啊,就是不停对边进行松弛

#include
#include
using namespace std;


const int N=2510,M=20000;

int h[N],ne[M],e[M],w[M],idx;
int d[N];
int backup[N];


int n,c,ts,td;

void add(int a,int b,int c)
{
    e[idx]=b;
    ne[idx]=h[a];
    w[idx]=c;
    h[a]=idx++;
}

void bellman_ford()
{
    memset(d,0x3f,sizeof d);
    
    d[ts]=0;
    
    for(int i=1;i<=n;i++)  //对n条边进行松弛
    {
        memcpy(backup,d,sizeof d);
        for(int j=1;j<=n;j++)  //对n个点都作为起点尝试松弛经过点j的点到源点的最短距离
        {
            for(int a=h[j];a!=-1;a=ne[a])
            {
                int b=e[a];
                d[b]=min(d[b],backup[j]+w[a]);
            }
        }
    }
}


int main()
{
    cin>>n>>c>>ts>>td;
    memset(h,-1,sizeof h);
    
    while(c--)
    {
        int a,b,c;
        cin>>a>>b>>c;
        add(a,b,c);
        add(b,a,c);
    }
    
    bellman_ford();
    cout<<d[td];
    
    return 0;
}

4.spaf算法
Bellman_ford算法会遍历所有的边,但是有很多的边遍历了其实没有什么意义,我们只用遍历那些到源点距离变小的点所连接的边即可,只有当一个点的前驱结点更新了,该节点才会得到更新;因此考虑到这一点,我们将创建一个队列每一次加入距离被更新的结点。

#include
#include
#include
using namespace std;


const int N=2510,M=20000;

int h[N],ne[M],e[M],w[M],idx;
int d[N];
bool st[N];


int n,c,ts,td;

void add(int a,int b,int c)
{
    e[idx]=b;
    ne[idx]=h[a];
    w[idx]=c;
    h[a]=idx++;
}

void spfa()
{
    memset(d,0x3f,sizeof d);
    queue<int>q;
    q.push(ts);
    d[ts]=0;
    st[ts]=true;
    while(!q.empty())
    {
        int t=q.front();
        q.pop();
        st[t]=false;
        for(int i=h[t];i!=-1;i=ne[i])
        {
            int j=e[i];
            if(d[j]>d[t]+w[i])  //如果从t到源点的距离加上t到j的距离小于j到源点的距离,那么进行松弛操作。
            {
                //注意,如果j在队列里面,不需要加入(队列里面的元素都是被其前驱点更新过的点,这些有用的点去继续更新其他点)
                d[j]=d[t]+w[i];
                if(!st[j])
                {
                    st[j]=true;
                    q.push(j);
                }
            }
        }
    }
}

int main()
{
    cin>>n>>c>>ts>>td;
    memset(h,-1,sizeof h);
    
    while(c--)
    {
        int a,b,c;
        cin>>a>>b>>c;
        add(a,b,c);
        add(b,a,c);
    }
    
    spfa();
    cout<<d[td];
    
    return 0;
}

2.1128.信使

单源最短路建图_第2张图片

任意选一个模板,找到点1到最远的那个点的距离

#include
#include
#include
#include

using namespace std;


const int N=110,M=410;

int h[N],e[M],ne[M],w[M],idx;

int d[N];
bool st[N];


int n,m;

void add(int a,int b,int c)
{
    e[idx]=b;
    ne[idx]=h[a];
    w[idx]=c;
    h[a]=idx++;
}

void spaf()
{
    memset(d,0x3f,sizeof d);
    queue<int>q;
    q.push(1);
    d[1]=0;
    st[1]=true;
    while(!q.empty())
    {
        int t=q.front();
        q.pop();
        
        st[t]=false;
        
        for(int i=h[t];i!=-1;i=ne[i])
        {
            int j=e[i];
            if(d[j]>d[t]+w[i])
            {
                d[j]=d[t]+w[i];
                if(!st[j])
                {
                    st[j]=true;
                    q.push(j);
                }
            }
        }
    }
}

int main()
{
    cin>>n>>m;
    memset(h,-1,sizeof h);
    
    while(m--)
    {
        int a,b,c;
        cin>>a>>b>>c;
        add(a,b,c);
        add(b,a,c);
    }
    spaf();
    
    int res=0;
    for(int i=1;i<=n;i++)
    {
        if(d[i]>0x3f3f3f3f/2)
        {
            res=-1;
            break;
        }
        else if(d[i]>res)
        {
            res=d[i];
        }
    }
    cout<<res<<endl;
    return 0;
    
}

floyd算法写的(yxc的方法)

#include 
#include 
#include 

using namespace std;

const int N = 110, INF = 0x3f3f3f3f;

int n, m;
int d[N][N];

int main()
{
    cin >> n >> m;

    memset(d, 0x3f, sizeof d);
    for (int i = 1; i <= n; i ++ ) d[i][i] = 0;
    for (int i = 0; i < m; i ++ )
    {
        int a, b, c;
        cin >> a >> b >> c;
        d[a][b] = d[b][a] = min(d[a][b], c);
    }

    for (int k = 1; k <= n; k ++ )
        for (int i = 1; i <= n; i ++ )
            for (int j = 1; j <= n; j ++ )
                d[i][j] = min(d[i][j], d[i][k] + d[k][j]);

    int res = 0;
    for (int i = 1; i <= n; i ++ )
        if (d[1][i] == INF)
        {
            res = -1;
            break;
        }
        else res = max(res, d[1][i]);

    cout << res << endl;

    return 0;
}


3.1127. 香甜的黄油

单源最短路建图_第3张图片

多源汇最短路问题:
标准做法:
1.floyd算法(n3)
2.使用n次dijkstra优化版(n* m *log2n)
3.使用 n次spaf算法(m * n)

思路:枚举所有点为起点,计算起点到所有牛的最短路径的和。
找出这些起点中之和最短的那个起点

#include
#include
#include
#include

using namespace std;


const int N=3000;

int h[N],e[N],ne[N],w[N],idx;
int d[N];
bool st[N];

int mm[N];

int n,p,c;//奶牛数 N,牧场数 P,牧场间道路数 C。


void add(int a,int b,int c)
{
    e[idx]=b;
    ne[idx]=h[a];
    w[idx]=c;
    h[a]=idx++;
}

void spaf(int start)
{
    memset(st,false,sizeof st);
    memset(d,0x3f,sizeof d);
    queue<int>q;
    q.push(start);
    st[start]=true;
    d[start]=0;
    while(!q.empty())
    {
        int t=q.front();
        q.pop();
        
        st[t]=false;
        for(int i=h[t];i!=-1;i=ne[i])
        {
            int j=e[i];
            if(d[j]>d[t]+w[i])
            {
                d[j]=d[t]+w[i];
                if(!st[j])
                {
                    st[j]=true;
                    q.push(j);
                }
            }
        }
    }
    
}


int main()
{
    cin>>n>>p>>c;
    for(int i=0;i<n;i++)
    {
        int x;
        cin>>x;
        mm[i]=x;
    }
    
    memset(h,-1,sizeof h);
    while(c--)
    {
        int a,b,c;
        cin>>a>>b>>c;
        add(a,b,c);
        add(b,a,c);
    }
    
    int res=0x3f3f3f3f;
    for(int i=1;i<=p;i++)  //枚举每个点作为汇集点
    {
        spaf(i);
        int sum=0;
        for(int j=0;j<n;j++)
        {
           if(d[mm[j]]==0x3f3f3f3f)
           {
               sum=0x3f3f3f3f;
               break;
           }
           sum+=d[mm[j]];
        }
        res=min(res,sum);
    }
    
    cout<<res;
    return 0;
    
}

4.903. 昂贵的聘礼

单源最短路建图_第4张图片

#include
#include
#include
using namespace std;


const int N=110;
int w[N][N],level[N];

int d[N];
bool st[N];

int n,m;


int dijkstra(int down,int up)
{
    memset(d,0x3f,sizeof d);
    memset(st,0,sizeof st);
    
    d[0]=0;
    for(int i=1;i<=n+1;i++)
    {
        int t=-1;
        for(int j=0;j<=n;j++)
            if(!st[j]&&(t==-1||d[t]>d[j]))
                t=j;
        st[t]=true;
        for(int j=1;j<=n;j++)
        {
            if(level[j]>=down&&level[j]<=up)
                d[j]=min(d[j],d[t]+w[t][j]);
        }
    
    }
    
    return d[1];
}


int main()
{
    cin>>m>>n;
    memset(w,0x3f,sizeof w);
    for(int i=1;i<=n;i++)
        w[i][i]=0;
    
    for(int i=1;i<=n;i++)
    {
        int price,cnt;
        cin>>price>>level[i]>>cnt;
        w[0][i]=min(w[0][i],price);
        
        while(cnt--)
        {
            int id,cost;
            cin>>id>>cost;
            w[id][i]=min(w[id][i],cost);
        }
        
    }
    
    int res=0x3f3f3f3f;
    for(int i=level[1]-m;i<=level[1];i++)
        res=min(res,dijkstra(i,i+m));
        
    cout<<res;
    return 0;
}

5.1126. 最小花费

单源最短路建图_第5张图片

#include
#include
#include

using namespace std;

const int N=200010;

int h[N],ne[N],idx,e[N];
double w[N];

double d[N];

bool st[N];



int n,m,ts,td;

void add(int a,int b,double c)
{
    e[idx]=b;
    ne[idx]=h[a];
    w[idx]=c;
    h[a]=idx++;
    
}

void spaf()
{
    queue<int>q;
    d[ts]=1.0;
    q.push(ts);
    st[ts]=true;
    while(!q.empty())
    {
        int t=q.front();
        q.pop();
        
        st[t]=false;
        
        for(int i=h[t];i!=-1;i=ne[i])
        {
            int j=e[i];
            if(d[j]<d[t]*w[i])
            {
                d[j]=d[t]*w[i];
                if(!st[j])
                {
                    st[j]=true;
                    q.push(j);
                }
                
            }
        }
    }
}

int main()
{
    cin>>n>>m;
    memset(h,-1,sizeof h);
    
    while(m--)
    {
        int a,b,c;
        cin>>a>>b>>c;
        double z=(100.0-c)/100;
        add(a,b,z);
        add(b,a,z);
        
    }
    
    cin>>ts>>td;
    
    spaf();
    printf("%.8lf",100/d[td]);
    return 0;
    
}

6.920. 最优乘车

单源最短路建图_第6张图片

#include
#include
#include
#include

using namespace std;


const int N=550;

int m,n;
bool g[N][N];
int d[N];
int stop[N];
int q[N];

void bfs()  //找到距离源点最近的点
{
    memset(d,0x3f,sizeof d);
    int hh=0,tt=0;
    d[1]=0;
    q[0]=1;
    while(hh<=tt)
    {
        int t=q[hh++];
        
        for(int i=1;i<=n;i++)
        {
            if(g[t][i]&&d[i]>d[t]+1)  //任意两个点的距离都被抽象为1
            {
                d[i]=d[t]+1;
                q[++tt]=i;
            }
        }
    }
}


int main()
{
    cin>>m>>n;
    string line;
    getline(cin,line);  //读取换行
    
    while(m--)
    {
        getline(cin,line);
        stringstream ss(line);
        int cnt=0,p;
        while(ss>>p)stop[cnt++]=p;
        
        for(int i=0;i<cnt;i++)
            for(int j=i+1;j<cnt;j++)
                g[stop[i]][stop[j]]=true;
    }
    
    bfs();
    
    if(d[n]==0x3f3f3f3f)puts("NO");
    else cout<<max(d[n]-1,0)<<endl;
    
    return 0;
    
    
}

你可能感兴趣的:(算法,图论,c++)