L3-1 森森旅游 (只拿了24 分)

首先这道题分没有拿满,但方向或许找对了?

![在这里插入图片描述](https://img-blog.csdnimg.cn/20210503150410991.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L20wXzU0MDY5Mzk4,size_16,color_FFFFFF,t_70

L3-1 森森旅游 (30 分)
好久没出去旅游啦!森森决定去 Z 省旅游一下。
Z 省有 n 座城市(从 1 到 n 编号)以及 m 条连接两座城市的有向旅行线路(例如自驾、长途汽车、火车、飞机、轮船等),每次经过一条旅行线路时都需要支付该线路的费用(但这个收费标准可能不止一种,例如车票跟机票一般不是一个价格)。
Z 省为了鼓励大家在省内多逛逛,推出了旅游金计划:在 i 号城市可以用 1 元现金兑换 a​i​​ 元旅游金(只要现金足够,可以无限次兑换)。城市间的交通即可以使用现金支付路费,也可以用旅游金支付。具体来说,当通过第 j 条旅行线路时,可以用 c​j​​ 元现金或 d​j​​ 元旅游金支付路费。注意: 每次只能选择一种支付方式,不可同时使用现金和旅游金混合支付。但对于不同的线路,旅客可以自由选择不同的支付方式。
森森决定从 1 号城市出发,到 n 号城市去。他打算在出发前准备一些现金,并在途中的某个城市将剩余现金 全部 换成旅游金后继续旅游,直到到达 n 号城市为止。当然,他也可以选择在 1 号城市就兑换旅游金,或全部使用现金完成旅程。
Z 省政府会根据每个城市参与活动的情况调整汇率(即调整在某个城市 1 元现金能换多少旅游金)。现在你需要帮助森森计算一下,在每次调整之后最少需要携带多少现金才能完成他的旅程。
输入格式:
输入在第一行给出三个整数 n,m 与 q(1≤n≤10​5​​,1≤m≤2×10​5​​,1≤q≤10​5​​),依次表示城市的数量、旅行线路的数量以及汇率调整的次数。
接下来 m 行,每行给出四个整数 u,v,c 与 d(1≤u,v≤n,1≤c,d≤10​9​​),表示一条从 u 号城市通向 v 号城市的有向旅行线路。每次通过该线路需要支付 c 元现金或 d 元旅游金。数字间以空格分隔。输入保证从 1 号城市出发,一定可以通过若干条线路到达 n 号城市,但两城市间的旅行线路可能不止一条,对应不同的收费标准;也允许在城市内部游玩(即 u 和 v 相同)。
接下来的一行输入 n 个整数 a​1​​,a​2​​,⋯,a​n​​(1≤a​i​​≤10​9​​),其中 a​i​​ 表示一开始在 i 号城市能用 1 元现金兑换 a​i​​ 个旅游金。数字间以空格分隔。
接下来 q 行描述汇率的调整。第 i 行输入两个整数 x​i​​ 与 a​i​′​​(1≤x​i​​≤n,1≤a​i​′​​≤10​9​​),表示第 i 次汇率调整后,x​i​​ 号城市能用 1 元现金兑换 a​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 号城市兑换旅游金。

题目真的长。。
首先看懂提以后我的第一想法就是Dijkstra找最短路径,但是即使是堆优化任然会超时(虽然最后还是有一个点超时。。。)。
首先我们先找到一个中间点 t 用来兑换旅游金,这样 1 - t 使用的是现金, t - n 使用的是旅游金,最后就可以得到一趟下来需要的现金。然而直接使用Dijkstra是不行的,因为这个图拥有两种代价:现金、旅游金。于是我想的是 :正着建立以现金作为权重的图,逆着建立以旅游金为权重的图,两遍Dijkstra得到两张图,这样的话我们就可以在询问修改的时候直接比较,值得注意的是我们没必要每次修改都遍历一遍中间点 t 只有当修改的是之前最优的中间点时我们才重新寻找最优点,否则就直接以被修改的点作为中间点与之前的最优点比较即可。

#include<iostream>
#include<queue>
#include<cstring>
#include<algorithm>
#include<limits.h>
using namespace std;
#define INF LLONG_MAX/2
typedef long long ll;
const int maxn = 2e5 + 5;
inline ll read() {
	ll s = 0, w = 1;
	char ch = getchar();
	while (ch == '-') w = -1, ch = getchar();
	while (ch >= '0'&&ch <= '9') s = s * 10 + ch - '0', ch = getchar();
	return s*w;
}
inline void write(ll x) {
	if (x < 0) putchar('-'), x = -x;
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
struct edge {//正向图
	int to;
	int next;
	int c;
}e[maxn];
struct reedge {//逆向图
	int to;
	int next;
	int d;
}ree[maxn];
struct node {
	ll dis;
	int pos;
	bool operator<(const node &a)const {
		return dis > a.dis;
	}
};
struct renode {
	ll dis;
	int pos;
	bool operator<(const renode &a)const {
		return dis > a.dis;
	}
};
int head[maxn], rehead[maxn];
int cnt, recnt;
int n, m, q;
ll ans = INF;
ll rate[maxn];
ll dis1[maxn], dis2[maxn];
ll set1[maxn], set2[maxn];
void init() {
	fill(head, head + n + 1, -1);
	fill(rehead, rehead + n + 1, -1);
	cnt = recnt = 0;
}
void addEdge(int x, int y, int c, int d) {
	e[cnt].to = y;
	e[cnt].c = c;
	e[cnt].next = head[x];
	head[x] = cnt++;
	/******************逆图**********************/
	ree[recnt].to = x;
	ree[recnt].d = d;
	ree[recnt].next = rehead[y];
	rehead[y] = recnt++;
}

void Heap_Dijk1(int s) {
	fill(dis1, dis1 + n + 1, INF);
	fill(set1, set1 + n + 1, 0);
	priority_queue<node> pq;
	dis1[s] = 0;
	pq.push({ dis1[s],s });//起点入队
	while (!pq.empty()) {
		node t = pq.top();
		pq.pop();
		if (set1[t.pos]) continue;
		set1[t.pos] = 1;
		for (int i = head[t.pos]; i != -1; i = e[i].next) {//寻找起点能到达的最近点
			int v = e[i].to;
			if (!set1[v] && dis1[v] > e[i].c + dis1[t.pos]) {
				dis1[v] = e[i].c + dis1[t.pos];
				pq.push({ dis1[v],v });
			}
		}
	}
}
void Heap_Dijk2(int s) {
	fill(dis2, dis2 + n + 1, INF);
	fill(set2, set2 + n + 1, 0);
	priority_queue<renode> pq;
	dis2[s] = 0;
	pq.push({ dis2[s],s });//起点入队
	while (!pq.empty()) {
		renode t = pq.top();
		pq.pop();
		if (set2[t.pos]) continue;
		set2[t.pos] = 1;
		for (int i = rehead[t.pos]; i != -1; i = ree[i].next) {//寻找起点能到达的最近点
			int v = ree[i].to;
			if (!set2[v] && dis2[v] > ree[i].d + dis2[t.pos]) {
				dis2[v] = ree[i].d + dis2[t.pos];
				pq.push({ dis2[v],v });
			}
		}
	}
}
int main() {
	n = read(), m = read(), q = read();
	init();
	for (int i = 1; i <= m; i+=1) {
		int x, y, c, d;

		//scanf("%d%d%d%d", &x, &y, &c, &d);
		x = read(), y = read(), c = read(), d = read();
		addEdge(x, y, c, d);
	}
	for (int i = 1; i <= n; i+=1) rate[i] = read();
	Heap_Dijk1(1);
	Heap_Dijk2(n);
	int index = -1;
	for (int i = 1; i <= q; i+=1) {
		int x, newr;
		x = read(), newr = read();
		rate[x] = newr;
		if (index == -1 || x == index) {//如果修改了之前最优的中间点再次进行判断
			ans = INF;
			for (int t = 1; t <= n; t+=1) {//找到修改兑换率时的最小现金花费
				ll temp = (dis2[t] % rate[t] == 0) ? (dis2[t] / rate[t]) : (dis2[t] / rate[t] + 1);
				if (ans > dis1[t] + temp) {
					index = t;
					ans = dis1[t] + temp;
				}
			}
		}
		else {
			ll temp = (dis2[x] % rate[x] == 0) ? (dis2[x] / rate[x]) : (dis2[x] / rate[x] + 1);
			if (ans > dis1[x] + temp) {
				index = x;
				ans = dis1[x] + temp;
			}
		}
		write(ans);
		putchar(10);
	}
	return 0;
}

我是菜鸡。。。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值