洛谷 P1576 最小花费



最小花费

题目描述

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 1n2000,m100000



思路解析

单源最短路问题,求 A A A B B B 之间最少的手续费比率是多少。
注意到数据量 n ⋅ m < 1 e 9 n·m < 1e9 nm<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 / (1z%)
  • 手续费的比率是累乘的,不是累加的!!!
    • 所以初始最短路要设置为 1 1 1
    • 当然,这个累乘是乘1 - z%这个数,代表还剩下来多少钱;而不是手续费的累乘z%,这样乘手续费给乘没了。
  • 一开始,我用w[]数组存的是z的值,因为z是正整数,所以用来求最短好像没什么问题,但是我忽略了一个问题:我怎么确定中间周转了几次?不知道周转了几次那手续费有几个%呢?无从得知(感觉可以用一个数组存,但感觉多此一举)。
  • 20
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值