团体程序设计天梯赛-练习集——L3-028 森森旅游 (30 分)

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

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

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

森森决定从 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 个整数 a 1 _{1} 1,a 2 _{2} 2,⋯,a n _{n} n(1≤a i _{i} ii​≤109),其中 a i _{i} i​表示一开始在 i 号城市能用 1 元现金兑换 a i _{i} i个旅游金。数字间以空格分隔。

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

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

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

输入样例:

6 11 3
1 2 3 5
1 3 8 4
2 4 4 6
3 1 8 6
1 3 10 8
2 3 2 8
3 4 5 3
3 5 10 7
3 3 2 3
4 6 10 12
5 6 10 6
3 4 5 2 5 100
1 2
2 1
1 17

输出样例:

8
8
1

样例解释:
对于第一次汇率调整,森森可以沿着 1→2→4→6 的线路旅行,并在 2 号城市兑换旅游金;

对于第二次汇率调整,森森可以沿着 1→2→3→4→6 的线路旅行,并在 3 号城市兑换旅游金;

对于第三次汇率调整,森森可以沿着 1→3→5→6 的线路旅行,并在 1 号城市兑换旅游金。
思路
1.正向建边从1跑最短路d1[i]
2.反向建边从n跑最短路d2[i]
3.枚举中转点的方式,得到在第i个城市将现金换成旅游金的情况下所需要的现金总额=1到i最短路+n到i最短路
trans[i]=trans[i]=d1[i]+(d2[i]+a[i]-1)/a[i];
然后答案用multiset维护即可
堆优化dijkstra算法优先队列

#include <bits/stdc++.h>
#define x first
#define y second
using namespace std;
typedef long long ll;
typedef pair<int,ll> PII;
const int N=1e5+5;
ll INF=1e18+1;//旅游先现金,再旅游金 
vector<PII> cash[N],travel[N];
bool st1[N],st2[N];
ll d1[N],d2[N],c,d,ai,a[N],trans[N];
int n,m,q,u,v,xi;
multiset<long long> minCost;
struct node{
	int id;
	ll dis;
	friend int operator < (const node &a, const node &b) {
			return a.dis > b.dis;
		}
};
priority_queue<node> Q;
void DIJ(int start,vector<PII> cash[],ll d[],bool st[]){
	fill(d,d+n+1,INF);
	d[start]=0;
	Q.push(node{start,0});
	while(Q.size()){
		int x=Q.top().id;
		Q.pop();
		if(st[x]) continue;
		st[x]=1;
		for(int i=0;i<cash[x].size();i++){
			if(d[cash[x][i].x]>d[x]+cash[x][i].y){
				d[cash[x][i].x]=d[x]+cash[x][i].y;
				Q.push(node{cash[x][i].x,d[cash[x][i].x]});
			}	
		}
	}
}
int main()
{	

	cin>>n>>m>>q;
	for(int i=0;i<m;i++){
		cin>>u>>v>>c>>d;
		cash[u].push_back({v,c});
		travel[v].push_back({u,d});
	}
	for(int i=1;i<=n;i++) cin>>a[i];
	DIJ(1,cash,d1,st1);
	DIJ(n,travel,d2,st2);
	
	for(int i=1;i<=n;i++){//枚举中转点
			if(d1[i]==INF||d2[i]==INF) continue;
			trans[i]=d1[i]+(d2[i]+a[i]-1)/a[i];//比整数多就得多换一块钱 
			minCost.insert(trans[i]);
	} 
	for(int i=0;i<q;i++){
		cin>>xi>>ai;
		if(a[xi]==ai||!trans[xi]) cout<<*minCost.begin()<<endl;
		else{
			minCost.erase(minCost.find(trans[xi]));
			a[xi]=ai;
			trans[xi]=d1[xi]+(d2[xi]+a[xi]-1)/a[xi];//比整数多就得多换一块钱 
			minCost.insert(trans[xi]);
			cout<<*minCost.begin()<<endl;	
		}	
	}
	return 0;
}

普通dijkstra 19分3个点运行超时+一个答案错误

#include <bits/stdc++.h>
#define x first
#define y second
using namespace std;
typedef long long ll;
typedef pair<int,ll> PII;
const int N=1e5+5;
ll INF=1e18+1;//旅游先现金,再旅游金 
vector<PII> cash[N],travel[N];
bool st1[N],st2[N];
ll d1[N],d2[N],c,d,ai,a[N],trans[N];;
int n,m,q,u,v,xi;
multiset<long long> minCost;
void DIJ(int start,vector<PII> cash[],ll d[],bool st[]){
	fill(d,d+n+1,INF);
	for(int i=0;i<cash[start].size();i++){
		d[cash[start][i].x]=cash[start][i].y;
	}
	st[start]=true;
	d[start]=0;
	for(int i=1;i<n;i++){
		int minpos=0;
		for(int j=1;j<=n;j++){
			if(!st[j]&&d[minpos]>d[j]) minpos=j;
		}
		st[minpos]=true;
		if(minpos==0) break;
		for(int j=0;j<cash[minpos].size();j++){
			if(!st[cash[minpos][j].x]&&d[cash[minpos][j].x]>d[minpos]+cash[minpos][j].y) 
			d[cash[minpos][j].x]=d[minpos]+cash[minpos][j].y;	
		}
	}
}
int main()
{	
	cin>>n>>m>>q;
	for(int i=0;i<m;i++){
		cin>>u>>v>>c>>d;
		cash[u].push_back({v,c});
		travel[v].push_back({u,d});
	}
	DIJ(1,cash,d1,st1);
	DIJ(n,travel,d2,st2);
	for(int i=1;i<=n;i++) cin>>a[i];
	
	for(int i=1;i<=n;i++){//枚举中转点
			if(d1[i]==INF||d2[i]==INF) continue;
			trans[i]=d1[i]+(d2[i]+a[i]-1)/a[i];//比整数多就得多换一块钱 
			minCost.insert(trans[i]);
	} 
	for(int i=0;i<q;i++){
		cin>>xi>>ai;
		if(a[xi]==ai||!trans[xi]) cout<<*minCost.begin()<<endl;
		else{
			
			minCost.erase(minCost.find(trans[xi]));
			a[xi]=ai;
			trans[xi]=d1[xi]+(d2[xi]+a[xi]-1)/a[xi];//比整数多就得多换一块钱 
			minCost.insert(trans[xi]);
			cout<<*minCost.begin()<<endl;
			
		}
		
	}
	
	return 0;
}
  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值