浅谈SPFA——洛谷P1576 最小花费 题解

想找原题请点击这里:传送门

原题:

题目描述
在n个人中,某些人的银行账号之间可以互相转账。这些人之间转账的手续费各不相同。给定这些人之间转账时需要从转账金额里扣除百分之几的手续费,请问A最少需要多少钱使得转账后B收到100元。

输入格式
第一行输入两个正整数n,m,分别表示总人数和可以互相转账的人的对数。

以下m行每行输入三个正整数x,y,z,表示标号为x的人和标号为y的人之间互相转账需要扣除z%的手续费 (z<100)。

最后一行输入两个正整数A,B。数据保证A与B之间可以直接或间接地转账。

输出格式
输出A使得B到账100元最少需要的总费用。精确到小数点后8位。

输入输出样例
输入 复制
3 3                                     
1 2 1
2 3 2
1 3 3
1 3
输出 复制
103.07153164
说明/提示
1<=n<=2000,m<=100000

首先看到这道题就想到是一道图论问题,并且类似最短路问题。但是不同的是我们这里求的不是最短路,而是最长路。

那么最短路径问题的话ROS首先想到dijkstra,所以在一开始便使用了dijkstra算法(思路一会儿再讲),然而写的dijkstra算法却只能得20分,剩下的点全部TLE。ROS一会儿在分析时会讲为何这道题如同ROS所写的dijkstra会TLE掉。

那么首先先分析一下这道题吧:

这道题我们看题目便发现是一道类单元最短路径问题。我们知道要从A向B转账100元,如我们需要计算从A向B转账X元实际到账多少则是需要计算从A到Bの”实际到账率“(即 ∏(1-手续率))。然后我们再用100元除以从A到B的最大的实际到账率,则可以算出来我们需要的最少的钱数了。

如果说我们使用dijkstra算法的话,开始则需要使一开始所有点的dis均为-1(此处的dis要为double数据类型,用来储存”实际到账率)。然后令dis[a]=1(即从A向A汇款实际到账率为100%,便于后面的计算)。然后用链式前向星一条条枚举以A为起点的边。并且当dis[tmp.to]<dis[nowid]*e[i].w时,令dis[tmp.to]=dis[nowid]*e[i].w。并且这一过程需要使用优先队列堆优化,但是我们很快就会发现这一过程的问题:Dijkstra算法在此处的体现即为我们从堆中不断取出两点间距离最大的线路并试图更新。但由于我们的dis是由路径上的几个最大的实际到账率相乘得到的的,故我们并不能保证几个最大的实际到账率相乘之后就一定大于几个较小的实际到账率相乘的值。比如说:0.9*0.85*0.8*0.75*0.7=0.3213,而0.65*0.6=0.39。所以说此处的dijkstra的堆优化毫无作用,使得Dijkstra退化成了O(n²)的最朴素的Dijkstra,所以计算时间便会长很多。

而由于“SPFA它死了”(SPFA算法在2018NOI D1T1中被数据卡死只能得20分。然后讲题人在讲题的时候屏幕上放着:“关于SPFA:它死了”)ROS以前在学习的时候认为Dijkstra是万能的所以ROS便没有深入学习SPFA算法。后来得知SPFA能够处理负边权问题并且在一些情况下SPFA能够处理Dijkstra不能够处理的问题(比如这道),所以ROS便考虑学习一下SPFA算法。

SPFA是什么?

SPFA是中国人自己发明的算法!

SPFA算法求最短路径的流程如下:

对于一个图来说,朴素的SPFA算法中创造一个队列。将每个点的dis值全部设置为0x3f(很大的数,可理解为无限大)然后用类似BFS的原理,将已经入队的此元素找到相应的后驱边。然后比较后驱点点dis值与上一个点的dis值+这条边的边权;如果此后驱点的dis值大于上一个点的dis值+这条边的边权,则更新dis值并且如果该顶点此时未入队,将其入队。所以这一过程的终点就是当队列为空时,操作结束。

这种做法显然可以处理负边权的情况,由于即使某一个点在之前被更新过,但由于SPFA算法的原理所以依然会进行比较从而试图更新,故可处理负边权。

代码实现:

 1 #include<bits/stdc++.h>
 2 #define N 2005
 3 #define M 100005
 4 using namespace std;
 5 double dis[N];
 6 bool vis[N];
 7 int n,m;
 8 int x,y;
 9 double z;
10 int a,b;
11 int tot;
12 int head[N]; 
13 struct tree{
14     int to;
15     int nxt;
16     double w;
17 }e[2*M];
18 struct node{
19     int f;    //现在在几号点 
20     double g;    //现在的距离 
21 };
22 queue <node> q;
23 void add(int u,int v,double r){
24     e[++tot].to=v;
25     e[tot].nxt=head[u];
26     head[u]=tot;
27     e[tot].w=(100-r)/100;
28 }
29 void SPFA(){
30     node tmp=(node){a,1};
31     dis[a]=1;
32     q.push(tmp);
33     while(!q.empty()){
34         tmp=q.front();
35         q.pop();
36         vis[tmp.f]=false;
37         for(int i=head[tmp.f];i;i=e[i].nxt){
38             int vv=e[i].to;
39             if(dis[vv]<dis[tmp.f]*e[i].w){
40                 dis[vv]=dis[tmp.f]*e[i].w;
41                 if(!vis[vv]){
42                     vis[vv]=true;
43                     q.push((node){vv,dis[vv]});
44                 }
45             }
46         }
47     }
48     return ;
49 }
50 int main(){
51     scanf("%d%d",&n,&m);
52     for(int i=1;i<=n;i++) dis[i]=-1;
53     for(int i=1;i<=m;i++){
54         scanf("%d%d%lf",&x,&y,&z);
55         add(x,y,z);
56         add(y,x,z);
57     }
58     scanf("%d%d",&a,&b);
59     SPFA();
60     printf("%.8lf",100/dis[b]);
61     return 0;
62 }
原文地址:https://www.cnblogs.com/robertspot/p/12375145.html