森森旅游的简单题解

原题:

好久没出去旅游啦!森森决定去 Z 省旅游一下。

Z 省有 n 座城市(从 1 到 n 编号)以及 m 条连接两座城市的有向旅行线路(例如自驾、长途汽车、火车、飞机、轮船等),每次经过一条旅行线路时都需要支付该线路的费用(但这个收费标准可能不止一种,例如车票跟机票一般不是一个价格)。

Z 省为了鼓励大家在省内多逛逛,推出了旅游金计划:在 i 号城市可以用 1 元现金兑换 ai​ 元旅游金(只要现金足够,可以无限次兑换)。城市间的交通即可以使用现金支付路费,也可以用旅游金支付。具体来说,当通过第 j 条旅行线路时,可以用 cj​ 元现金 dj​ 元旅游金支付路费。注意: 每次只能选择一种支付方式,不可同时使用现金和旅游金混合支付。但对于不同的线路,旅客可以自由选择不同的支付方式。

森森决定从 1 号城市出发,到 n 号城市去。他打算在出发前准备一些现金,并在途中的某个城市将剩余现金 全部 换成旅游金后继续旅游,直到到达 n 号城市为止。当然,他也可以选择在 1 号城市就兑换旅游金,或全部使用现金完成旅程。

Z 省政府会根据每个城市参与活动的情况调整汇率(即调整在某个城市 1 元现金能换多少旅游金)。现在你需要帮助森森计算一下,在每次调整之后最少需要携带多少现金才能完成他的旅程。

输入格式:

输入在第一行给出三个整数 n,m 与 q(1≤n≤105,1≤m≤2×105,1≤q≤105),依次表示城市的数量、旅行线路的数量以及汇率调整的次数。

接下来 m 行,每行给出四个整数 u,v,c 与 d(1≤u,v≤n,1≤c,d≤109),表示一条从 u 号城市通向 v 号城市的有向旅行线路。每次通过该线路需要支付 c 元现金或 d 元旅游金。数字间以空格分隔。输入保证从 1 号城市出发,一定可以通过若干条线路到达 n 号城市,但两城市间的旅行线路可能不止一条,对应不同的收费标准;也允许在城市内部游玩(即 u 和 v 相同)。

接下来的一行输入 n 个整数 a1​,a2​,⋯,an​(1≤ai​≤109),其中 ai​ 表示一开始在 i 号城市能用 1 元现金兑换 ai​ 个旅游金。数字间以空格分隔。

接下来 q 行描述汇率的调整。第 i 行输入两个整数 xi​ 与 ai′​(1≤xi​≤n,1≤ai′​≤109),表示第 i 次汇率调整后,xi​ 号城市能用 1 元现金兑换 ai′​ 个旅游金,而其它城市旅游金汇率不变。请注意:每次汇率调整都是在上一次汇率调整的基础上进行的。

输出格式:

对每一次汇率调整,在对应的一行中输出调整后森森至少需要准备多少现金,才能按他的计划从 1 号城市旅行到 n 号城市。

再次提醒:如果森森决定在途中的某个城市兑换旅游金,那么他必须将剩余现金全部、一次性兑换,剩下的旅途将完全使用旅游金支付。

 题意:有n个城市,m条有向路径,每条路径有两种权值,一个是现金支付,还有一个是旅游金支付,在某个城市可以将手中的现金全部转换成旅游金,每个城市现金与旅游金之间的汇率由a1...an存储,有q次修改,每次会更改一个城市的汇率,对于每次修改,问从1号点到n号点最少要花多少钱。

题解思路:把每一个点看成中转点,先从1号点用现金到达中转点,在从中转点用旅游金到n点,得到旅游金再转化成现金求1号点到n号点总共花了多少。对于从1号点到其他中转点,可以直接以正常最短路的方法得出,从中转点到n号点,需要看成从n号点到中转点,这意味着在存储图的时候,需要分两个图,一个按照常规存储现金的路径,另一个按照前一个图去反向图,比如1->2现金需要10元,旅游金需要20元,就可以分别在两张图上建立1->2权值为10的路径和2->1权值为20的路径。求最短路需要使用Dijkstra算法求,用spfa可能会超时。对于每个作为中转站的点得到的所需要的现金结果,用multiset来存储,它可以自动排序方便找到最小值。

#include <iostream>
#include <algorithm>
#include <cmath>
#include<cstring>
#include<queue>
#include<map>
#include<cstdlib>
#include<cstdio>
#include<string>
#define ll long long
#include<stack>
#include <vector>
#include<unordered_map>
#include<set>
using namespace std;
const int maxn=1e5+10;
const ll inf=1e18;
int n,m,ci;//n个城市,m条路径,ci次修改修改
bool vis1[maxn],vis2[maxn];//dijkstra所需要的,记录这个节点是否被访问过
ll dis1[maxn],dis2[maxn];
multiset<ll>mincost;//用于维护每个中转点的结果
struct node{
	ll to,w;
};//存储边
struct good{
	ll id;//记录点
	ll dis;//记录距离
	friend bool operator <(const good&a,const good&b)//保证队列顶部是距离最小的点
	{
		return a.dis>b.dis;
	}
};//优先队列的元素类型
vector<node>road1[maxn],road2[maxn];
void add1(int u,ll to,ll w)//存储现金的路径
{
	road1[u].push_back({to,w});
}
void add2(int u,ll to,ll w)//存储旅游金的路径
{
	road2[u].push_back({to,w});
}
priority_queue<good>q;//优先队列
void dijkstra(int start,vector<node>road[],ll dis[],bool vis[])
{//start是起点,road[]是图,dis代表距离,vis记录是否访问过该点
	for(int i=0;i<=n;++i)//初始化
	dis[i]=inf;
	dis[start]=0;//起点距离为0
	q.push({start,0});//加入队列
	while(!q.empty())
	{
		int p=q.top().id;//取出第一个点
		q.pop();//删除点
		if(vis[p])
		continue;
		vis[p]=1;
		for(int i=0;i<road[p].size();i++)
		{
			ll to=road[p][i].to;
			ll w=road[p][i].w;
			if(dis[to]>dis[p]+w)//放缩
			{
				dis[to]=dis[p]+w;
				q.push({to,dis[to]});
			}
		}
	}
}
ll a[maxn];//记录每个点的汇率
int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);//关闭同步流
cin>>n>>m>>ci;//输入
for(int i=0;i<m;i++)
{
	ll u,to,w,w2;
	cin>>u>>to>>w>>w2;
	add1(u,to,w);
	add2(to,u,w2);
}
for(int i=1;i<=n;i++)
cin>>a[i];
dijkstra(1,road1,dis1,vis1);
dijkstra(n,road2,dis2,vis2);
for(int i=1;i<=n;i++)
if(dis1[i]!=inf&&dis2[i]!=inf)//如果可以通过i点然后到n点
{
    ll ans=dis1[i]+(dis2[i]+a[i]-1)/a[i];
	mincost.insert(ans);//(dis2[i]+a[i]-1)/a[i]保证了如果 
 //dis2[i]不能整除a[i]时,得到的现金加1。
}
while(ci--)
{
	int dian,v;//dian为修改汇率的城市,改成v。
	cin>>dian>>v;
	if(a[dian]==v||dis1[dian]==inf||dis2[dian]==inf)//如果修改的城市到不了或者v和原来的值相 
  //比没变化
	cout<<*mincost.begin()<<'\n';//输出最小值
	else
	{
		mincost.erase(mincost.find(dis1[dian]+(dis2[dian]+a[dian]-1)/a[dian]));
            //删除该中转站所得的原本的值
		a[dian]=v;//修改汇率
		mincost.insert(dis1[dian]+(dis2[dian]+a[dian]-1)/a[dian]);//添加现在所需要的现金
		cout<<*mincost.begin()<<'\n';//输出最小值
	}
	
}
	return 0;
} 

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值