最小花费
题目描述
在 n n n 个人中,某些人的银行账号之间可以互相转账。这些人之间转账的手续费各不相同。给定这些人之间转账时需要从转账金额里扣除百分之几的手续费,请问 A A A 最少需要多少钱使得转账后 B B B 收到 100 100 100 元。
输入格式
第一行输入两个正整数 n , m n,m n,m,分别表示总人数和可以互相转账的人的对数。
以下 m m m 行每行输入三个正整数 x , y , z x,y,z x,y,z,表示标号为 x x x 的人和标号为 y y y 的人之间互相转账需要扣除 z % z\% z% 的手续费 ( z < 100 ) (z<100) (z<100)。
最后一行输入两个正整数 A , B A,B A,B。数据保证 A A A 与 B B B 之间可以直接或间接地转账。
输出格式
输出 A A A 使得 B B B 到账 100 100 100 元最少需要的总费用。精确到小数点后 8 8 8 位。
样例 #1
样例输入 #1
3 3
1 2 1
2 3 2
1 3 3
1 3
样例输出 #1
103.07153164
提示
1 ≤ n ≤ 2000 , m ≤ 100000 1\le n \le 2000,m\le 100000 1≤n≤2000,m≤100000。
思路解析
单源最短路问题,求
A
A
A 到
B
B
B 之间最少的手续费比率是多少。
注意到数据量
n
⋅
m
<
1
e
9
n·m < 1e9
n⋅m<1e9,所以放心用
S
P
F
A
SPFA
SPFA。
CODE
#include <iostream>
#include <vector>
#include <cstring>
#include <algorithm>
#include <queue>
#define ll long long
#define INF 0x3f3f3f3f
using namespace std;
typedef pair<int, int> pii;
const int N = 2020, M = 2e5 + 10;
int h[N], e[M], ne[M], idx; // 定义图的存储结构
bool st[N]; // 存储每个节点是否在队列中
int n, m; // n是节点数,m是边的数目
double dist[N], w[M]; // 将dist和w改为double类型
// 添加一条边
void add(int a, int b, double c){
e[idx] = b; // 边的终点
w[idx] = 1 - c / 100.0; // 将手续费的百分比转换为小数
ne[idx] = h[a]; // 下一条相同起点的边
h[a] = idx++; // 更新起点a的最后一条边
}
// SPFA算法,用于求解单源最短路径
double spfa(int x, int y){
memset(dist, INF, sizeof dist); // 初始化所有节点到源点的距离为无穷大
dist[x] = 1.0; // 源点到自己的距离为1.0
queue<int> q;
q.push(x); // 将源点加入队列
st[x] = true; // 标记源点已经在队列中
while(q.size()){
auto t = q.front(); // 取出队首元素
q.pop();
st[t] = false; // 标记t已经不在队列中
for(int i = h[t]; i != -1; i = ne[i]){ // 遍历所有从t出发的边
int j = e[i];
if(dist[j] < dist[t] * w[i]){ // 如果可以通过t到j的距离小于当前的最短距离
dist[j] = dist[t] * w[i]; // 更新最短距离
if(!st[j]){ // 如果j不在队列中
q.push(j); // 将j加入队列
st[j] = true; // 标记j已经在队列中
}
}
}
}
return dist[y]; // 返回从x到y的最短路径长度
}
int main()
{
memset(h, -1, sizeof h); // 初始化邻接表
cin >> n >> m; // 输入节点数,边的数目
while (m -- ){
int a, b;
double c;
scanf("%d%d%lf", &a, &b, &c); // 输入边的信息
add(a, b, c), add(b, a, c); // 将边添加到图中
}
int x, y;
scanf("%d%d", &x, &y); // 输入源点和目标点
double z = spfa(x, y); // 执行SPFA算法,求解最短路径
printf("%.8f", 100 / z); // 输出结果
}
一些细节考虑
- 由公式可推最后的结果公式,下次麻烦您想好了再写代码OK? A = 100 / ( 1 − z % ) A = 100\ /\ (1 - z\%) A=100 / (1−z%)
- 手续费的比率是累乘的,不是累加的!!!
- 所以初始最短路要设置为 1 1 1。
- 当然,这个累乘是乘
1 - z%
这个数,代表还剩下来多少钱;而不是手续费的累乘z%
,这样乘手续费给乘没了。
- 一开始,我用
w[]
数组存的是z
的值,因为z
是正整数,所以用来求最短好像没什么问题,但是我忽略了一个问题:我怎么确定中间周转了几次?不知道周转了几次那手续费有几个%
呢?无从得知(感觉可以用一个数组存,但感觉多此一举)。