单源最短路的综合应用

目录

新年好

 通信线路

 道路与航线

 最优贸易

新年好

重庆城里有 n 个车站,m 条 双向 公路连接其中的某些车站。

每两个车站最多用一条公路连接,从任何一个车站出发都可以经过一条或者多条公路到达其他车站,但不同的路径需要花费的时间可能不同。

在一条路径上花费的时间等于路径上所有公路需要的时间之和。

佳佳的家在车站 1,他有五个亲戚,分别住在车站 a,b,c,d,e

过年了,他需要从自己的家出发,拜访每个亲戚(顺序任意),给他们送去节日的祝福。

怎样走,才需要最少的时间?

输入格式

第一行:包含两个整数 n,m 分别表示车站数目和公路数目。

第二行:包含五个整数 a,b,c,d,e 分别表示五个亲戚所在车站编号。

以下 m 行,每行三个整数 x,y,t,表示公路连接的两个车站编号和时间。

输出格式

输出仅一行,包含一个整数 T,表示最少的总时间。

数据范围

1≤n≤50000
1≤m≤10^5
1 1≤x,y≤n
1≤t≤100

输入样例:

6 6
2 3 4 5 6
1 2 8
2 3 3
3 4 4
4 5 5
5 6 2
1 6 7

输出样例:

21

思路:

先预处理 在爆搜

单源最短路的综合应用_第1张图片

 dist是一个二维数组,分别存0和其他5个点到其他点的最短路,你memset会清空之前跑的结果,但是4*N就只是把二位数组中的一个变成0x3f

#include
#include
#include
#include
using namespace std;
const int N=5e4+10,M=2e5+10,INF=1e9+10;
typedef pairPII;
int h[N],ne[M],e[M],w[M],idx;
int dist[6][N];
bool st[N];
int source[6];
int n,m;
//建表
void add(int a,int b,int c)
{
    e[idx]=b;
    w[idx]=c;
    ne[idx]=h[a];
    h[a]=idx++;
}
//遍历1 a b c d e 到其余各点的最短距离
//dijkstra堆优化
void dijkstra(int start,int pos)
{
    memset(st,0,sizeof st);
    memset(dist[pos],0x3f,sizeof dist[pos]);//这样只会初始化 dist[0][1~N],或者dist[1][1~N]...
    dist[pos][start]=0;
    priority_queue,greater>heap;
    heap.push({0,start});
    while(heap.size())
    {
        auto t=heap.top();
        heap.pop();
        int ver=t.second,distance=t.first;
        if(st[ver]) continue;
        st[ver]=true;
        for(int i=h[ver];i!=-1;i=ne[i])
        {
            int j=e[i];
            if(dist[pos][j]>dist[pos][ver]+w[i])
            {
                dist[pos][j]=dist[pos][ver]+w[i];
                heap.push({dist[pos][j],j});
            }
        }
    }
}
int res=INF;
//6个数全排列 u表示几个数 start表示当前是第几个数 distance 表示距离 6个数5个距离
void dfs(int u,int start,int distance)
{
    if(u==6)
    {
        res=min(res,distance);
    }
    for(int i=1;i<=5;i++)
    {
        int next=source[i];
        if(!st[i])
        {
            st[i]=true;
            dfs(u+1,i,distance+dist[start][next]);
            st[i]=false;
        }
    }
}
int main()
{
    scanf("%d%d",&n,&m);
    source[0]=1;
    for(int i=1;i<=5;i++) scanf("%d",&source[i]);
    memset(h,-1,sizeof h);
    while(m--)
    {
        int a,b,c;scanf("%d%d%d",&a,&b,&c);
        add(a,b,c);
        add(b,a,c);
    }
    for(int i=0;i<=5;i++) dijkstra(source[i],i);
    //在初始化一次 因为前面的堆优化都用了
    memset(st,0,sizeof st);
    dfs(1,0,0);
    printf("%d\n",res);
    return 0;
}

 通信线路

在郊区有 N 座通信基站,P 条 双向 电缆,第 i 条电缆连接基站 Ai 和 Bi。

特别地,1 号基站是通信公司的总站,N 号基站位于一座农场中。

现在,农场主希望对通信线路进行升级,其中升级第 i 条电缆需要花费 Li。

电话公司正在举行优惠活动。

农产主可以指定一条从 1 号基站到 N 号基站的路径,并指定路径上不超过 K 条电缆,由电话公司免费提供升级服务。

农场主只需要支付在该路径上剩余的电缆中,升级价格最贵的那条电缆的花费即可。

求至少用多少钱可以完成升级。

输入格式

第 1 行:三个整数 N,P,K。

第 2..P+1 行:第 i+1 行包含三个整数 Ai,Bi,Li。

输出格式

包含一个整数表示最少花费。

若 1 号基站与 N 号基站之间不存在路径,则输出 −1。

数据范围

0≤K 1≤P≤10000
1≤Li≤1000000

输入样例:

5 7 1
1 2 5
3 1 4
2 4 8
3 2 3
5 2 9
3 4 7
4 5 6

输出样例:

4

 单源最短路的综合应用_第2张图片

 题意:

求从起点 1到 终点 N 找一条代价最小的路径(最短路问题),每一条路径(1->N)的代价是这条路(组成这条路的某个节点到某个节点)的最大权值,但是可以有k条边免费,可以让k 条边权值为零

思路:

首先可以明确的是这道题目考察最短路算法(题目都说了求1->N中花费最少)

但是与以往直接求1->N 这条路径代价不同的是 它求的不是全部的边之和最小

而是求得是 每一条路径的最大边是这条路径的花费

并且在这条路径中可以让(1~K)条边不花费 可以设置一种特殊边 让(1~K)条边权值为0 

所以可以设置一个数组dist[x][p] 表示 节点1到节点x 途中设置p条边权值为0

新加入的边是非0边.
那么我们面对每一条新加入的边(x,y,z)我们的d[y,p]=max(d[x,p],z)其中z为(x,y)权值.

新加入的边是0边.
如果新加入的边是权值为0的边,显然是d[y,p+1]=d[x,p].

max(dis[t][p],z)这个地方是表示新加入的这个边是需要掏钱的,所以就要和前面走过的边所用的费用进行对比,选取中最大的费用;dis[t][p-1]这个表示这个新加入的边免费升级,则新加入一条边需要支付的费用,还是前面的费用没有改变;min(dis[t][p-1],max(dis[t][p],z)),这里应该是指,新加入的这边,免费修和不免费修,两种方案的费用中取最小值;

#include
#include
#include
#include
using namespace std;
const int N=1010,M=2e5+10,INF=0x3f3f3f3f;
int h[N],ne[M],e[M],w[M],idx;
int dist[N][N];//dist[x][p]表示节点1到节点x 途中经历了p条权值为0的边的最小值
bool st[N];
int n,p,k;
int ans=INF;
//建表
void add(int a,int b,int c)
{
    e[idx]=b;
    w[idx]=c;
    ne[idx]=h[a];
    h[a]=idx++;
}
void spfa()
{
    memset(dist,0x3f,sizeof dist);
    memset(st,0,sizeof st);
    dist[1][0]=0;//节点1到节点1的距离为0 并且此时没有用清零 没有设特殊边
    queueq;
    q.push(1);
    st[1]=1;
    //松弛原理
    while(q.size())
    {
        int t=q.front();
        q.pop();
        st[t]=0;
        for(int i=h[t];i!=-1;i=ne[i])
        {
            int j=e[i],z=w[i];
            int x=max(dist[t][0],z);//最初都没有设特殊边 1-t->j 取最大值
            if(dist[j][0]>x)//若是x小则更新
            {
                dist[j][0]=x;
                if(!st[j])
                {
                    q.push(j);
                    st[j]=1;
                }
            }
            //1->...->j 设特殊边 1~k 找最小值
            for(int p=1;p<=k;p++)
            {
            /*dist[t][p-1] 表示从 1->...->t->j 是不需要花费的免费的这需要设t->j
            边权为0 故dist[t][p]=dist[t][p-1];
            而 max(dist[t][p],z)说明 t->j这条边是需要花钱的(在1->...->t的途中已经经过了p条边权为0的边)
            所以就要和前面走过的边所用的费用进行对比,选取中最大的费用
            这里表示的意思是新加入的边 免费修和不免费修 两种方案取最小
            */
                 int y=min(dist[t][p-1],max(dist[t][p],z));
                 if(dist[j][p]>y)
                 {
                     dist[j][p]=y;
                    if(!st[j])
                    {
                        q.push(j);
                        st[j]=1;
                    }
                 }
            }
        }
    }
}
int main()
{
    scanf("%d%d%d",&n,&p,&k);
    //建表初始化
    memset(h,-1,sizeof h);
    while(p--)
    {
        int a,b,c;scanf("%d%d%d",&a,&b,&c);
        add(a,b,c);
        add(b,a,c);
    }
    spfa();
    //找达到1->n中最小值
    for(int i=0;i<=k;i++) ans=min(ans,dist[n][i]);
    if(ans==INF) cout<<"-1"<

可以用二分做 但是目前还没有彻底搞懂 ~ - ~

#include 
#include 
#include 
using namespace std;
typedef pair  PII;
const int N = 1010,M = 20010;
int n,m,k;
int h[N],ne[M],idx = 0;
struct edge {
    int to,w;
}edges[M];
int dist[N];
bool st[N];
void add (int a,int b,int c) {
    edges[idx] = {b,c};
    ne[idx] = h[a];
    h[a] = idx++;
}
bool check (int max_w) {
    memset (dist,0x3f,sizeof (dist));
    memset (st,false,sizeof (st));
    dist[1] = 0;
    priority_queue ,greater > heap;
    heap.push ({0,1});
    while (!heap.empty ()) {
        int t = heap.top ().second;
        heap.pop ();
        if (st[t]) continue;
        st[t] = true;
        for (int i = h[t];~i;i = ne[i]) {
            int j = edges[i].to,w = edges[i].w > max_w;
            if (dist[j] > dist[t] + w) {
                dist[j] = dist[t] + w;
                heap.push ({dist[j],j});
            }
        }
    }
    return dist[n] <= k;
}
int main () {
    memset (h,-1,sizeof (h));
    scanf ("%d%d%d",&n,&m,&k);
    int max_w = 0;
    while (m--) {
        int a,b,c;
        scanf ("%d%d%d",&a,&b,&c);
        max_w = max (max_w,c);
        add (a,b,c),add (b,a,c);
    }
    int l = 0,r = max_w + 1;
    while (l < r) {
        int mid = l + r >> 1;
        if (check (mid)) r = mid;
        else l = mid + 1;
    }
    if (l == max_w + 1) printf ("-1\n");
    else printf ("%d\n",l);
    return 0;
}

 道路与航线

农夫约翰正在一个新的销售区域对他的牛奶销售方案进行调查。

他想把牛奶送到 T 个城镇,编号为 1∼T。

这些城镇之间通过 R 条道路 (编号为 1 到 R) 和 P 条航线 (编号为 1 到 P) 连接。

每条道路 i 或者航线 i 连接城镇 Ai 到 Bi,花费为 Ci。

对于道路,0≤Ci≤10,000 然而航线的花费很神奇,花费 Ci 可能是负数(−10,000≤Ci≤10,000)。

道路是双向的,可以从 Ai 到 Bi,也可以从 Bi 到 Ai,花费都是 Ci。

然而航线与之不同(单向),只可以从 Ai 到 Bi。

事实上,由于最近恐怖主义太嚣张,为了社会和谐,出台了一些政策:保证如果有一条航线可以从 Ai 到 Bi,那么保证不可能通过一些道路和航线从 Bi 回到 Ai

由于约翰的奶牛世界公认十分给力,他需要运送奶牛到每一个城镇。

他想找到从发送中心城镇 S 把奶牛送到每个城镇的最便宜的方案。

输入格式

第一行包含四个整数 T,R,P,S

接下来 R 行,每行包含三个整数(表示一个道路)Ai,Bi,Ci

接下来 PP 行,每行包含三个整数(表示一条航线)Ai,Bi,Ci

输出格式

第 1..T 行:第 i 行输出从 S 到达城镇 i 的最小花费,如果不存在,则输出 NO PATH

数据范围

1≤T≤25000
1≤R,P≤50000
1≤Ai,Bi,S≤T

输入样例:

6 3 3 4
1 2 5
3 4 5
5 6 10
3 5 -100
4 6 -100
1 3 -10

输出样例:

NO PATH
NO PATH
5
0
-95
-100
#include 
#include 
#include 
#include 
#include 

#define x first
#define y second

using namespace std;

typedef pair PII;

const int N = 25010, M = 150010, INF = 0x3f3f3f3f;

int n, mr, mp, S;
int id[N];
int h[N], e[M], w[M], ne[M], idx;
int dist[N], din[N];
vector block[N];
int bcnt;
bool st[N];
queue q;

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

void dfs(int u, int bid)
{
    id[u] = bid, block[bid].push_back(u);

    for (int i = h[u]; ~i; i = ne[i])
    {
        int j = e[i];
        if (!id[j])
            dfs(j, bid);
    }
}

void dijkstra(int bid)
{
    priority_queue, greater> heap;

    for (auto u : block[bid])
        heap.push({dist[u], u});

    while (heap.size())
    {
        auto t = heap.top();
        heap.pop();

        int ver = t.y, distance = t.x;
        if (st[ver]) continue;
        st[ver] = true;

        for (int i = h[ver]; ~i; i = ne[i])
        {
            int j = e[i];
            if (id[j] != id[ver] && -- din[id[j]] == 0) q.push(id[j]);
            if (dist[j] > dist[ver] + w[i])
            {
                dist[j] = dist[ver] + w[i];
                if (id[j] == id[ver]) heap.push({dist[j], j});
            }
        }
    }
}

void topsort()
{
    memset(dist, 0x3f, sizeof dist);
    dist[S] = 0;

    for (int i = 1; i <= bcnt; i ++ )
        if (!din[i])
            q.push(i);

    while (q.size())
    {
        int t = q.front();
        q.pop();
        dijkstra(t);
    }
}

int main()
{
    cin >> n >> mr >> mp >> S;
    memset(h, -1, sizeof h);

    while (mr -- )
    {
        int a, b, c;
        cin >> a >> b >> c;
        add(a, b, c), add(b, a, c);
    }

    for (int i = 1; i <= n; i ++ )
        if (!id[i])
        {
            bcnt ++ ;
            dfs(i, bcnt);
        }

    while (mp -- )
    {
        int a, b, c;
        cin >> a >> b >> c;
        din[id[b]] ++ ;
        add(a, b, c);
    }

    topsort();

    for (int i = 1; i <= n; i ++ )
        if (dist[i] > INF / 2) cout << "NO PATH" << endl;
        else cout << dist[i] << endl;

    return 0;
}

 最优贸易

C 国有 n 个大城市和 m 条道路,每条道路连接这 n 个城市中的某两个城市。

任意两个城市之间最多只有一条道路直接相连(没有重边)

这 m 条道路中有一部分为单向通行的道路,一部分为双向通行的道路,双向通行的道路在统计条数时也计为 1 条。

C 国幅员辽阔,各地的资源分布情况各不相同,这就导致了同一种商品在不同城市的价格不一定相同。

但是,同一种商品在同一个城市的买入价和卖出价始终是相同的。

商人阿龙来到 C 国旅游。

当他得知“同一种商品在不同城市的价格可能会不同”这一信息之后,便决定在旅游的同时,利用商品在不同城市中的差价赚一点旅费。

设 C 国 n 个城市的标号从 1∼n,阿龙决定从 1 号城市出发,并最终在 n 号城市结束自己的旅行。

在旅游的过程中,任何城市可以被重复经过多次,但不要求经过所有 n 个城市。

阿龙通过这样的贸易方式赚取旅费:他会选择一个经过的城市买入他最喜欢的商品——水晶球,并在之后经过的另一个城市卖出这个水晶球,用赚取的差价当做旅费。

因为阿龙主要是来 C 国旅游,他决定这个贸易只进行最多一次,当然,在赚不到差价的情况下他就无需进行贸易。

现在给出 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≤各城市水晶球价格≤100

输入样例:

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

输出样例:

5

 单源最短路的综合应用_第3张图片

单源最短路的综合应用_第4张图片 最一般的最短路维护的是路径上的sum性质,本题维护的是max和min性质,sum性质具有累加性(就是要从前面的值基础上累加,后续出现只会越来越大,所以第一次出现的就是最短),而max 和min对于新出现的数,单独比较即可,所以不能用dijkstra(dijkstra就是利用的sum的累加性)

总的来说就是max和min,后面出现的数不一定比前面的数都差(而dijkstra的sum性质能保证后面出现的数都比前面的数都差)  

 为什么不能使用Dijkstra

能使用Dijkstra的原则是,当我们从小根堆拿出距离最小的点,更新其他的邻接点的边,是可以保证它未来不会被其他点更新的更小的。固非负权图可以满足,因为但凡后面的点还能到此点,只能让距离不变或更大。

那这道题,题目没有保证没有环,那么找寻最小的情况,可能第一次到达点 i,此时它前面的点更新到它,最小的确定出来了是dmin[i],使用 dijkstra更新完后如果真的把它锁定,日后仍可能遇到更小的点,且能走回dmin[i],从某种角度讲,这道题并不是看边权而是看点权故只要走到一个相对比之前dmin[i]小的点,且还能走回i,就相当于有了负权边,故此时dijkstra 失效。

 为什么必须反向建图

由图结构到DP 

我们这道题首先根据题意判断,价值首先不在边权,而是点权。其次本题说所有点可以经过 无限 次,故并不是一次最短路解决问题说明存在非最短路最优的情况,要么需要改造最短路算法,要么需要组合一些别的算法。

因为阿龙主要是来 C 国旅游,他决定这个贸易只进行最多一次,当然,在赚不到差价的情况下他就无需进行贸易。故直觉思路就是找寻所有到 N 的路径,然后把路径中最大的点权减去最小的点权。

使用 DP 的思想,我们可以找寻到达点 i的最小点权【包括i】,然后找寻从点i到达点n的最大点权,然后进行求差值

那为什么反向建图?只用正向图难道不能算i到n了不成? 

有这个疑问的,需要好好思考一下,最短路算的距离,最后得到的d[n],是1 -> n的最短路,我们初始化距离为无穷,然后初始化起点为 0,所以最短路算法算的是固定起点,算到所有终点的距离。

故我们如果想用正向图算i -> n,那岂不是每枚举到一个点 i,都需要算一次正向最短路,才能算出 i -> n。

但如果反向建图求最短路
我们反向建图,可以求得n -> 任何点的最短路,但我们知道,实际的边是正向的,故反向图的n -> 任何点的最短路等价于实际的正向图任何点 -> n的最短路

故我们使用了反向图,就把一个O(n ^ 2)问题,变成了O(2n)的问题。这里指的是两个建图方式的最短路复杂度对比,而不是实际的复杂度。

实际情况下,瓶颈是SPFA,SPFA算法的时间复杂度是O(km),其中k一般情况下是个很小的常数,最坏情况下是n,n表示总点数,m表示总边数。因此总时间复杂度是 O(km),至于两次spfa也算是常数,就归入 k 中了。

 正向图正着走一遍 =(-)倒着走一遍

正向图正着走一遍=反向图倒着走一遍

#include
#include
#include
#include
using namespace std;
const int N=1e5+10,M=2e6+10,INF=0x3f3f3f3f;
//h[]正向图 hr[]反向图
int h[N],hr[N],e[M],ne[M],idx;
int price[N];
bool st[N];
//从1~i 过程中,买入水晶球的最低价格 dmin[],从i~n过程中卖出水晶球的最高价格
int dmin[N],dmax[N];
//建图
void add(int h[],int a,int b)
{
    e[idx]=b;
    ne[idx]=h[a];
    h[a]=idx++;
}
int n,m;
void spfa(int d[],int start,int h[],bool flag)
{
    queueq;
    memset(st,0,sizeof st);
    
    if(flag)  memset(d,0x3f,sizeof dmin);
  
   q.push(start);
   d[start]=price[start];
   st[start]=true;
  
   while(q.size())
   {
       int t=q.front();
       q.pop();
       st[t]=false;
       for(int i=h[t];i!=-1;i=ne[i])
       {
           int j=e[i];
           if(flag&&d[j]>min(d[t],price[j])||!flag&&d[j]
#include
#include
using namespace std;
vectorway_min[110000];
vectorway_max[110000];//我这里用vector记录数组,大家可以使用邻接矩阵
int value[110000], mins[110000], maxs[110000];
//value[i]代表城市i的水晶球价格
//mins[i]表示从1走到i的过程中,买入水晶球的最低价格
//maxs[i]表示从i走到n的过程中,卖出水晶球的最高价格
void dfs_min(int x, int m)
{
    if(m >= mins[x]) return;//该点已有更优解,无需再次搜索
    m = min(m, value[x]);
    mins[x] = m;//更新最优解
    for(int i = 0; i < way_min[x].size(); i++)
    {
        dfs_min(way_min[x][i], m);//遍历相邻的点
    }
}
void dfs_max(int x, int m)//这个函数和上一个函数差不多,这里不再赘述
{
    if(m <= maxs[x]) return;
    m = max(m, value[x]);
    maxs[x] = m;
    for(int i = 0; i < way_max[x].size(); i++)
    {
        dfs_max(way_max[x][i], m);
    }
}
int main()
{
    int n, m, x, y, z, ans = 0;
    cin >> n >> m;
    for(int i = 1; i <= n; i ++)
    {
        cin >> value[i];
        mins[i] = 999999999;
        maxs[i] = -999999999;//先取一下极值,也就是初始化
    }
    for(int p = 1; p <= m; p++)
    {
        cin >> x >> y >> z;
        way_min[x].push_back(y);//这里表示求最小值所需的路径
        way_max[y].push_back(x);//因为最大值是从n开始(从i到n可转化为从n到i),所以我们把所有路径倒个头
        if(z == 2)//双向边
        {
            way_min[y].push_back(x);
            way_max[x].push_back(y);
        }

    }
    dfs_min(1, value[1]);//从1开始遍历最小值
    dfs_max(n, value[n]);//从n开始遍历最大值
    for(int i = 1; i <= n; i ++) ans = max(ans, maxs[i] - mins[i]);
    cout << ans << endl;
    return 0;
}

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