一、引言

 

1.求最短路的几种算法,Dijkstra,Floyd,spfa等,总之他们都有一个共同点,就是他们更新的条件一样。条件均为:

    if(dis[edge[i].e]>dis[u]+len) dis[edge[i].e]=dis[u]+len;

dis[edge[i].e]表示要更新的点,dis[u]表示当前的点,len表示两点距离。更新的条件即为:要更新的点维护的权值【距起点的距离】通过这次更新能不能变小,若变小就去更新。从而到最后dis数组中dis[i]就表示为1到i的最短距离。PS:算法还不懂的话,这个专题置顶有详解。

 

2.如果更新条件发生改变的话,结果肯定会发生变化。为什么要改变更新条件呢?比如说最简单的例子:把最短路改为最长路,这个时候就应该每次更新把他放大才行,所以这时候条件变为:

    if(dis[edge[i].e]<dis[u]+len) dis[edge[i].e]=dis[u]+len;

就是>变成<,因为需要每次都更新他的最大值。

 

3.如果不是以上那么简单的变化,而是让你保留从1到n过程中你经过的最短的路的长度,这个时候更新条件应该怎么变化呢?

 

二、条件更新

 

例题为例:

 

1.NOPI2009最优贸易

题目描述

C 国有 n 个大城市和 m 条道路,每条道路连接这 n 个城市中的某两个城市。任意两个城市之间最多只有一条道路直接相连。这 m 条道路中有一部分为单向通行的道路,一部分为双向通行的道路,双向通行的道路在统计条数时也计为 1 条。
C 国幅员辽阔,各地的资源分布情况各不相同,这就导致了同一种商品在不同城市的价格不一定相同。但是,同一种商品在同一个城市的买入价和卖出价始终是相同的。
商人阿龙来到 C 国旅游。当他得知同一种商品在不同城市的价格可能会不同这一信息之后,便决定在旅游的同时,利用商品在不同城市中的差价赚回一点旅费。设 C 国 n 个城市的标号从 1~ n,阿龙决定从 1 号城市出发,并最终在 n 号城市结束自己的旅行。在旅游的过程中,任何城市可以重复经过多次,但不要求经过所有 n 个城市。阿龙通过这样的贸易方式赚取旅费:他会选择一个经过的城市买入他最喜欢的商品――水晶球,并在之后经过的另一个城市卖出这个水晶球,用赚取的差价当做旅费。由于阿龙主要是来 C 国旅游,他决定这个贸易只进行最多一次,当然,在赚不到差价的情况下他就无需进行贸易。
假设 C 国有 5 个大城市,城市的编号和道路连接情况如下图,单向箭头表示这条道路为单向通行,双向箭头表示这条道路为双向通行。

假设 1~n 号城市的水晶球价格分别为 4,3,5,6,1。
阿龙可以选择如下一条线路:1->2->3->5,并在 2 号城市以 3 的价格买入水晶球,在 3号城市以 5 的价格卖出水晶球,赚取的旅费数为 2。
阿龙也可以选择如下一条线路 1->4->5->4->5,并在第 1 次到达 5 号城市时以 1 的价格买入水晶球,在第 2 次到达 4 号城市时以 6 的价格卖出水晶球,赚取的旅费数为 5。
现在给出 n 个城市的水晶球价格,m 条道路的信息(每条道路所连接的两个城市的编号以及该条道路的通行情况)。请你告诉阿龙,他最多能赚取多少旅费。

 

输入

第一行包含 2 个正整数 n 和 m,中间用一个空格隔开,分别表示城市的数目和道路的数目。
第二行 n 个正整数,每两个整数之间用一个空格隔开,按标号顺序分别表示这 n 个城市的商品价格。

接下来 m 行,每行有 3 个正整数,x,y,z,每两个整数之间用一个空格隔开。如果 z=1,表示这条道路是城市 x 到城市 y 之间的单向道路;如果 z=2,表示这条道路为城市 x 和城市y 之间的双向道路。

1≤n≤100000,1≤m≤500000,1≤x,y≤n,1≤z≤2,1≤各城市水晶球价格≤100。

 

输出

输出共 1 行,包含 1 个整数,表示最多能赚取的旅费。如果没有进行贸易,则输出 0。

 

样例输入

复制样例数据

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

样例输出

5

思路:这个题目就是刚刚提到的记录路径中的最小权值最大权值,这个题目将每一个点维护的权值【即水晶球价格】输入进去,然后记录从1到i路径中经过点的最小权值与最大权值,这俩的差便是可以获得的最大利润,这里注意:最大权值肯定要在最小权值的后面,因为卖点要在买点的后面,并且要回到n。

 

核心算法:

const int INF=1e9;
const int maxn=1e6+5;
int minn[maxn];
void spfa1(int n)
{
    for(int i=1;i<=n;i++) minn[i]=INF;//初始每一个点维护的权值都放到最大,准备擂台法比较。
    queue<int>q;
    q.push(1);
    minn[1]=save[1];//让第一个点等于他的权值,save储存的每一个点的权值
    vis[1]=true;//spfa算法的标记,以下也不多说。
    while(!q.empty())//开始更新
    {
        int u=q.front();
        q.pop();
        vis[u]=false;
        for(int i=head[u];i!=-1;i=edge[i].next)//链式前向星
        {
            int x=min(minn[u],save[edge[i].e]);//当前点与下一个点的权值比较。
            if(minn[edge[i].e]>x)//更新
            {
                minn[edge[i].e]=x;
                if(vis[edge[i].e]==false)
                {
                    vis[edge[i].e]=true;
                    q.push(edge[i].e);
                }
            }
        }
    }
}

 

PS:其实这个题的思路简单,就是实现难,minn数组表示从起点到i的路径中最小权值,这个算法如何实现,与最短路的思想一样,每一步都去更新。举个简单的例子:a,b,c从a到c的最小权值是多少呢?a肯定是a,minn[1]=a,继续更新minn[2],minn[2]=min(minn[1],b)。这样推下去的话每一个点经过的都会被记录在minn数组中。但是根据最短路算法初始化,刚开始必须要全部初始为最大值,然后minn[now]与min(minn[pre],save[now])比较确定更不更新。然后注意一下卖点的确定是从n到i的最大值【自行理解叭】,这道题就完成了。

 

2.HDU 3790

给你n个点,m条无向边,每条边都有长度d和花费p,给你起点s终点t,要求输出起点到终点的最短距离及其花费,如果最短距离有多条路线,则输出花费最少的。

input 

输入n,m,点的编号是1~n,然后是m行,每行4个数 a,b,d,p,表示a和b之间有一条边,且其长度为d,花费为p。最后一行是两个数 s,t;起点s,终点。n和m为0时输入结束。
(1<n<=1000, 0<m<100000, s != t)

思路:这题比最短路的条件多了一项,多了一项花费,属于多条件最短路,多条件最短路必须要有主次关系,这道题是距离优先,所以咱们只需要再增加一个pay数组,花费数组。这个数组的更新怎么更新呢?因为距离优先所以优先更新最短路,如果最短路相同再去更新花费。

核心代码:

void spfa(int bg,int n)//以spfa为例
{
    for(int i=1;i<=n;i++) dis[i]=INF,vis[i]=false,pay[i]=0;
    dis[bg]=0;
    queue<int>q;
    q.push(bg);
    vis[bg]=true;
    while(!q.empty())
    {
        int u=q.front();
        q.pop();
        vis[u]=false;
        for(int i=head[u];i!=-1;i=edge[i].next)
        {
            int e=edge[i].e;
            if(dis[u]+edge[i].d<=dis[e])//要包含最短路相等更新花费的条件
            {            
                if(dis[u]+edge[i].d<dis[e])
                {
                    dis[e]=dis[u]+edge[i].d;
                    if(vis[e]==false)
                    {
                        vis[e]=true;
                        q.push(e);
                    }
                    pay[e]=pay[u]+edge[i].p;//花费跟着更新
                }
                else
                    pay[e]=min(pay[e],pay[u]+edge[i].p);//如果到这个点的最短路相等,更新多条最短路的最小。
            }
        }
    }
}

PS:这个题目也就玩成了,不过是一个多条件最短路,记住主次关系就可以了!

 

3.【kuangbin】带飞专题4-heavy transport

 

说实话,这个题刚开始确实没看懂什么意思,想了好久才AC,因为题目全是英文,我就说一下AC了的中文意思。

题目大意:有N个点,m条边相连,每一条边都有一个权值,求从1到n路径中最小权值中的最大值。如果说1-n一共有两条路径,

第一条最小权值是4,另一个是5,则维护5.

 

思路:这个题比起第一个类型的又增加了一点难度,难在了最小权值中,他还要新增一个最大值。我们可以将dis数组的含义改变,我们让dis数组不再记录最小权值,直接让dis数组记录最小权值中的最大值。所以dis应该初始化为0.首先对1连接的点进行初始化,因为1为起点所以与1相连的点刚开始更新应该就是他们与1想连的边的权值。我们更新的是什么呢?dis[now]=max(dis[now],min(dis[pre],edge[i].w),后者表示新来的路径中最小的权值,前者为现在的最小的权值,两者之间维护最大的权值即为本题的思路,所以代码:

void spfa(int n)
{
    for(int i=1;i<=n;i++) dis[i]=0,vis[i]=false;
    queue<int>q;
    dis[1]=INF;//新路径中的最小权值是min(dis[pre],edge[i].w)所以1要赋值为无穷这样才可以更新下面的点。
    q.push(1);
    while(!q.empty())
    {
        int u=q.front();
        q.pop();
        vis[u]=false;
        for(int i=head[u];i!=-1;i=edge[i].next)
        {
            int e=edge[i].e;
            int minl=min(dis[u],edge[i].w);//新的路径中的最小权值
            if(dis[e]<minl)//更新
            {
                dis[e]=minl;
                if(vis[e]==false)
                {
                    q.push(e);
                    vis[e]=true;
                }
            }
        }
    }
}

这里有一道求路径中最大权值最小值的最短路题目哦 

 

三、总结

1.通过这三个题,总结了几个简单的最短路类型,维护最小权值的最短路,多条件的最短路,维护不同路径中最小权值最大值的最短路。

2.首先是思想,想到更新条件之后,想办法实现,实现过程中注意对起点的初始化,有时候就是因为起点的赋值而卡壳,比如说第三个题,因为一个最大值最小值,一个dis数组我以为解决不了,想了好久。到最后发现改一下起点就可以更新。

3.不难发现,第一题是维护点的权值,第三题是维护边的权值,维护点的权值与维护边的权值不同点有待总结。