洛谷P1576 最小花费

这道题本质上就是一道最短路的变种,转账时候收取1%手续费可以理解为这条边边权为0.99,而且边权是相乘而不是相加

为了更好理解,这里解释一下样例:要求从1到3,如果从1到2,手续费1%,那么钱数就乘0.99;2到3再乘0.98,这样总倍数是0.9702,优于直接1给3的0.97倍,所以答案是100/0.97

p.s.注意不是转化率,是到账100元最少需要的总费用!!!

这是我用priority_queue优化过的dijkstra算法写的代码

关于dijkstra算法的详细知识可以看P4779的题解

#include <bits/stdc++.h>
using namespace std;
struct edge
{
	int to,nxt;
	double dis;
}e[100002];
double head[2002],dis[2002];
bool vis[100002];
int n,m,s,cnt,a,b;
void addedge(int u,int v,int d)
{
	e[++cnt].dis=1.0-d/100.0;
	e[cnt].to=v;
	e[cnt].nxt=head[u];
	head[u]=cnt;
}
struct node
{
	double dis;
	int pos;
	bool operator <(const node &x)const
    {
		return x.dis<dis;
	}
};
priority_queue<node>q;
void init()
{
	for(int i=1;i<=n;i++)
    {
		dis[i]=0x7fffffff;
    }
}
void dijkstra()
{
	dis[a]=1.0;
	q.push((node){1.0,a});
	while(!q.empty())
    {
		node tmp=q.top();
		q.pop();
		int x=tmp.pos;
		if(vis[x])continue;
		vis[x]=1;
		for(int i=head[x];i;i=e[i].nxt)
        {
			int y=e[i].to;
			if(dis[y]>dis[x]*e[i].dis)
            {
				dis[y]=dis[x]*e[i].dis;
				if(!vis[y])
                {
                    q.push((node){dis[y],y});
                }
			}
		}
	}
}
int main() 
{
	int i;
	cin>>n>>m;
	init();
	for(i=0;i<m;i++)
    {
		int u,v,d;
		scanf("%d%d%d",&u,&v,&d);
		addedge(u,v,d);
	}
	cin>>a>>b;
	dijkstra();
	printf("%.8lf",100/dis[b]);
	return 0;
}

copy题解的同志们发现“祖国江山一片红”——而且样例还过了,是不是很神奇? 这是为什么呢?

让我们来分析一下题目

bug1:要求对方到账100元且自己花费最少,那么就是要求总倍数最高,也就是说

求的是最长路而不是最短路!!!!!

所以我们需要在松弛操作时找尽可能长的边,同时把运算符“<”重载成“>”,保证找到的路径最长

于是copy题解的同志们把这两处改了却发现“大红灯笼高高挂” 都在bug后面标序号1了,怎么可能没有bug2呢?

bug2:继续读题icon-default.png?t=N7T8https://www.luogu.org/problem/P1576,可以发现题目中的第一行描述是:

在n个人中,某些人的银行账号之间可以互相转账。

互相!!!

由于这份代码的核心部分是P4779(单源最短路径)的变体,所以之前写的时候一直当转账是单向的在处理,其实这题的图是无向图,也就是说我们要 双向建边!

于是copy题解的同志加了一个addedge后发现“万紫千红总是春” 事实上因为建一条双向边相当于两条单向边,所以我们所有关于边的数组空间都要开2倍 (其实我所有数组都开了2倍)

献上码风奇特的代码(保证AC)

#include <bits/stdc++.h>
using namespace std;
struct edge
{
	int to,nxt;
	double dis;
}e[200010];
double head[4010],dis[4010];
bool vis[200010];
int n,m,s,cnt,a,b;
void addedge(int u,int v,int d)//建边
{
	e[++cnt].dis=1.0-d/100.0;
	e[cnt].to=v;
	e[cnt].nxt=head[u];
	head[u]=cnt;
}
struct node
{
	double dis;
	int pos;
	bool operator <(const node &x)const
    {
		return x.dis>dis;  //注意重载成大于!!!
	}
};
priority_queue<node>q;
void init()    //初始化
{
	for(int i=1;i<=n;i++)
    {
		dis[i]=0;   //注意不要设置成正无穷!!!
    }
}
void dijkstra()   //优先队列优化的dijkstra
{
	dis[a]=1.0;
	q.push((node){1.0,a});
	while(!q.empty())
    {
		node tmp=q.top();
		q.pop();
		int x=tmp.pos;
		if(vis[x])continue;
		vis[x]=1;
		for(int i=head[x];i;i=e[i].nxt)
        {
			int y=e[i].to;
			if(dis[y]<dis[x]*e[i].dis)   //注意这里是发现更大的dis时更新!!!
            {  
				dis[y]=dis[x]*e[i].dis;
				if(!vis[y])
                {
                    q.push((node){dis[y],y});
                }            
			}
		}
	}
}
int main() 
{
	int i;
	cin>>n>>m;
	init();
	for(i=0;i<m;i++)
    {
		int u,v,d;
		scanf("%d%d%d",&u,&v,&d);
		addedge(u,v,d);
		addedge(v,u,d);  //注意建边2次!!!
	}
	cin>>a>>b;
	dijkstra();
	printf("%.8lf",100.0/dis[b]);  //注意是100/dis[b]
	return 0;
}
  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值