CSP-迪杰斯特拉和相关变式(UVA - 11374)

CSP-迪杰斯特拉算法和变形求解最短路问题

知识简述

迪杰斯特拉(Dijkstra)算法,是一种求解正权边单源最短路的常用算法,稍有了解的人都比较熟悉,这里稍作简述Dij算法的实现过程。
1、首先我们要明确在进行迪杰斯特拉算法的过程中要维护的数据:一个小根堆用来存储当前已经更新过的点,一个dis[n]数组用来存储源点s到其他点的最短距离,一个vis[n]记录每个点是否被更新过。
2、现在开始算法的具体步骤:将dis[n]全部置为inf(极大值),dis[s]=0;将s点放入空的小根堆,并置vis[s]=1。
3、每次从小根堆中取出根元素a(最小值),并遍历该点的邻接边,如果对某个点y和边w(a,y),有dis[y]>dis[a]+w(a,y),则说明该点可以更新,一般在迪杰斯特拉算法中将更新的操作成为松弛,某个点被松弛要完成两个操作:更新dis[y]和将y加入小根堆中。
4、重复操作3,直至小根堆为空。遍历dis[n]数组,其值即为最短路距离,如果有点其dis[]值仍为inf,说明该点不可达。
迪杰斯特拉的算法特点:由于所有边均为正边,由此有如果堆先后弹出了a和b点,则一定有dis[a]<=dis[b],因此不可能存在dis[b]+正边<dis[a]。这个特点可以总结为Dij算法最显著的特征:所有点只会被弹出一次,且只要某个点被弹出,说明到该点的距离已经是最小值。
具体实现:在使用Dij过程中,用到了小根堆数据结构,但考虑到小根堆的初始化比较复杂,可以使用大根堆,将(-dis[i])传入大根堆即可起到与小根堆相同的效果。
拓展:单源正值最长路Dij做法
Dij由于其松弛的性质,也可以用来计算单源最长路,但有几个tips需要注意:
1、计算最长路,松弛操作应该变为dis[y]<dis[a]+w(a,y),且存储点集使用大根堆
2、在最长路的计算时,已经不再满足只要某个点被弹出,说明到该点的距离已经是最大值这条性质,每个点可以被多次弹出。
3、由于在计算最长路时,每个点会被多次弹出,这时有可能出现无意义的更新,为了提高Dij的性能,在出现堆顶x距离<dis[x],说明该点已经被其他更新过,不是最长路了,因此更新无意义,可以直接剪枝去除。
Dijkstra基本实现(C++)

const int inf=1e8;
const int M=1e6;
const int N=1e5;
struct Edge
{
    
    
    int to,nxt,value;
}e[M];

int head[N];
int tot=1;
int dis[N];
int n;//点数
int m;//边数
void init(int num)
{
    
    
    for(int i=1;i<=num;i++)
    head[i]=-1;
}
void add(int x,int y,int value)
{
    
    
    tot++;
    e[tot].to=y;
    e[tot].value=value;
    e[tot].nxt=head[x];
    head[x]=tot;
}
priority_queue<pair<int,int>> q;
void dijkstra(int s)
{
    
    
    while(q.size()) q.pop();
    //初始化
    for(int i=1;i<=n;I++)
    vis[i]=0,dis[i]=inf;
    dis[s]=0;
    q.push(make_pair(0,s));
    //初始化堆和访问数组
    while(q.size())
    {
    
    
        int x=q.top().second;
        q.pop();
        //每个点只能被弹出一次
        //在dijkstra的最短路中,一个点被堆弹出一次,则它已经到达最短路
        if(vis[x]==1) continue;
        vis[x]=1;
        //取出堆
        for(int i=head[x];i>=0;i=e[i].nxt)
        {
    
    
            int y=e[i].to;
            int value=e[i].value;
            if(dis[y]>dis[x]+value)//松弛
            {
    
    
                dis[y]=dis[x]+value;
                q.push(make_pair(-dis[y],y));
            }
        }
    }
}

题目概述

众所周知,TT 有一只魔法猫。
今天他在 B 站上开启了一次旅行直播,记录他与魔法猫在喵星旅游时的奇遇。 TT 从家里出发,准备乘坐猫猫快线前往喵星机场。猫猫快线分为经济线和商业线两种,它们的速度与价钱都不同。当然啦,商业线要比经济线贵,TT 平常只能坐经济线,但是今天 TT 的魔法猫变出了一张商业线车票,可以坐一站商业线。假设 TT 换乘的时间忽略不计,请你帮 TT 找到一条去喵星机场最快的线路,不然就要误机了!

INPUT&输入样例

输入包含多组数据。每组数据第一行为 3 个整数 N, S 和 E (2 ≤ N ≤ 500, 1 ≤ S, E ≤ 100),即猫猫快线中的车站总数,起点和终点(即喵星机场所在站)编号。
下一行包含一个整数 M (1 ≤ M ≤ 1000),即经济线的路段条数。
接下来有 M 行,每行 3 个整数 X, Y, Z (1 ≤ X, Y ≤ N, 1 ≤ Z ≤ 100),表示 TT 可以乘坐经济线在车站 X 和车站 Y 之间往返,其中单程需要 Z 分钟。
下一行为商业线的路段条数 K (1 ≤ K ≤ 1000)。
接下来 K 行是商业线路段的描述,格式同经济线。
所有路段都是双向的,但有可能必须使用商业车票才能到达机场。保证最优解唯一。
输入样例:

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

OUTPUT&输出样例

对于每组数据,输出3行。第一行按访问顺序给出 TT 经过的各个车站(包括起点和终点),第二行是 TT 换乘商业线的车站编号(如果没有使用商业线车票,输出"Ticket Not Used",不含引号),第三行是 TT 前往喵星机场花费的总时间。
输出样例:

1 2 4
2
5

题目重述

仔细读题我们可以将题目概括如下:
两张全正边的有向有权图,一张图A可以随意行动,一张图B只能挑选一条边走,从a到b的最短路径和路径点。

思路概述(分层Dij或枚举变式)

将题目分解我们可以发现:
如果去掉带有限制的一张图B,就是一道简单的Dij板子题,但加入了这张限制图后就会对路径产生影响,A图上的某些路径可能可以通过替换B上的一条边变得更短。
这里列举两种做法:
枚举法:适用于B上只能选1条边的情况
由于最多只能从B中选出一条,也就是选择情况是0/1,我们可以将问题化简成:
1、分别从两个端点出发对A图进行两次Dij,得到两个距离数组。
2、枚举B图中的每一条边,并进行相应的松弛更新,遍历完B中所有边即可确定是否选择或选择哪条边。
问题:如果B图可以选择n条边,则无法解决。
分层Dij:稍后更新

题目源码(C++)

枚举+Dij方法源码

#include<iostream>
#include<stdio.h>
#include<queue>
#include<string>
#include<vector>
using namespace std;
const int M=1e4+5;
const int N=1e3+5;
const int inf=1e8;
int point_number;//点个数
int eco_line;//经济线条数
int busin_line;//商务线条数
int eco_tot;//经济线add计数
int busin_tot;//商务线add计数
bool cout_flag=true;
vector<int> vec;
struct Edge{
    
    
    int to,txt,value;
};
Edge eco_edge[M];//经济线边集
Edge busin_edge[M];//商务线边集

int eco_head[N];
int busin_head[N];

void eco_init()
{
    
    
    for(int i=0;i<=point_number;i++)
    eco_head[i]=-1;
}
void eco_add(int x,int y,int value)
{
    
    
    eco_tot++;
    eco_edge[eco_tot].to=y;
    eco_edge[eco_tot].value=value;
    eco_edge[eco_tot].txt=eco_head[x];
    eco_head[x]=eco_tot;
}
int vis[N];
int pre[N];
int back[N];

priority_queue<pair<int,int>> q;

void dijkstral(int s,int* dis,int*pre)
{
    
    
    while(q.size()) q.pop();

    for(int i=1;i<=point_number;i++)
    dis[i]=inf,vis[i]=0,pre[i]=0;

    dis[s]=0;
    vis[s]=0;
    pre[s]=s;
    q.push(make_pair(0,s));

    while(q.size())
    {
    
    
        int x=q.top().second;
        q.pop();
        if(vis[x]==1) continue;
        vis[x]=1;
        for(int i=eco_head[x];i;i=eco_edge[i].txt)
        {
    
    
            int y=eco_edge[i].to;
            int value=eco_edge[i].value;
            if(dis[y]>dis[x]+value)
            {
    
    
                dis[y]=dis[x]+value;
                pre[y]=x;
                q.push(make_pair(-dis[y],y));
            }
        }
    }
}
int main()
{
    
    
    cout_flag=false;
	while(cin>>point_number)
    {
    
    
	int start,end=0;

    eco_tot=0;
    busin_tot=0;

    cin>>start>>end;
    cin>>eco_line;
    eco_init();
    for(int i=0;i<eco_line;i++)
    {
    
    
        int x,y,z;
        scanf("%d %d %d",&x,&y,&z);
        eco_add(x,y,z);
        eco_add(y,x,z);
    }
    int dis_start[N];
    int dis_end[N];
    dijkstral(start,dis_start,pre);
    dijkstral(end,dis_end,back);
    vec.clear();
    int dis_min=dis_start[end];
    int switch_start=0;
    int switch_end=0;
    int switch_line=0;
    cin>>busin_line;
    for(int i=0;i<busin_line;i++)
    {
    
    
        int x,y,z;
        scanf("%d %d %d",&x,&y,&z);
        if(dis_start[x]+dis_end[y]+z<dis_min)
        {
    
    
            dis_min=dis_start[x]+dis_end[y]+z;
            switch_start=x;
            switch_end=y;
            switch_line=1;
        }
        if(dis_start[y]+dis_end[x]+z<dis_min)
        {
    
    
            dis_min=dis_start[y]+dis_end[x]+z;
            switch_start=y;
            switch_end=x;
            switch_line=1;
        }
    }

    if(cout_flag==true) printf("\n");
    else cout_flag=true;

    if(switch_line==0)
    {
    
    
        int flag=end;
        while(flag!=start)
        {
    
    
            vec.push_back(flag);
            flag=pre[flag];
        }
        vec.push_back(start);
        for(int i=vec.size()-1;i>=1;i--)
        printf("%d ",vec[i]);
        printf("%d\nTicket Not Used\n",vec[0]);
        printf("%d\n",dis_min);
    }
    else
    {
    
    
    	int flag=switch_start;
        while(flag!=start)
        {
    
    
            vec.push_back(flag);
            flag=pre[flag];
        }
        vec.push_back(start);
        flag=switch_end;
        while(flag!=end)
        {
    
    
            vec.insert(vec.begin(),flag);
            flag=back[flag];
        }
        vec.insert(vec.begin(),end);
        for(int i=vec.size()-1;i>=1;i--)
        printf("%d ",vec[i]);
        printf("%d\n%d\n",vec[0],switch_start);
        printf("%d\n",dis_min);
	}
    vec.clear();
	}
    return 0;
}
/*
6 1 6
4
1 2 3
2 3 4
3 5 3
5 6 4
1
2 5 5
6 1 6
4
1 2 3
2 3 4
3 5 3
5 6 4
1
2 5 8
*/

猜你喜欢

转载自blog.csdn.net/qq_43942251/article/details/105379741