最短路跑两遍
参考思路:
由题意得每次兑换旅行币都要将现金兑完,所以可以将拆解成两段不同的最短路,一条是从1点出发到i点代表用现金的最短路,直接dijkstra即可;另一条则是从i点到达n点的代表用旅行货币的最短路,反向建图再dijkstra即可。
再枚举每个点作为兑换点时从1~n所需要的现金res[i]并加入multise中,每次修改汇率就是将res[i]删掉,再重新计算res[i]加入到multise中,multise.begin()即为每次修改后的现金最小花费值。之所以用multiset是因为可能有多个相同的值,但我们不能只保存一个,同时set可以自动排序,并且方便删除元素
个人思路:
本题的核心主要在于如果找到中转点,考虑到n的范围不大,我们可以枚举所有中转点,并计算出以该点作为中转点需要的最少花费,又因为起点一定是1,终点一定是n。考虑到每个点都可能是中转点,所以我们要求出起点1到所有点的dist,所有点到n的dist,也就是说我们要创建两次图,正反各一次。在保存答案的时候我们不能进行排序,否则O(n*q*logn)的时间复杂度肯定超时,所以我们要用一个容器来动态排序(set,map,priority_queue)因为优先队列不方便删除,map浪费空间,set最为适宜,同时因为可能存在相同值,要使用multiset。
一些需要注意的点
- 由于本题需要建两个图,所以边数要开两倍,之前点数不变过了,但隔了一段时间提交Wrong了,开了两倍点数又过了,说明点数也是要开两倍的。。
- 由于本题的val值很大,1e9,所以要开longlong,很多地方要换成longlong,容易忽视
- 关于set,我重新写的时候,删除元素直接用的set.erase(val),看起来没毛病是吧?但别忘了,我们用的时multiset,我们的目的就是要保存相同的值,但直接set.erase(val)的话,他会把所有等于val的值都删掉,而我们的本意是只删除一个,那怎么办呢?可以结合set的find函数,find函数返回第一个等于val的迭代器,我们set.erase(s.find(val))就可以实现只删除第一个等于val的元素了
代码
#include <iostream>
#include <cstring>
#include <algorithm>
#include <set>
#include <vector>
#include <queue>
#include <cmath>
#define x first
#define y second
using namespace std;
typedef long long LL;
typedef pair<LL, int> PII;
const int N = 200010, M = 400010;//因为要建两个图,所以点和边的数量要开两倍
const LL INF = 0x3f3f3f3f3f3f3f3fll;
int n, m, q;
int a[N], radio[N];
int h1[N], h2[N], e[M], ne[M], w[M], idx;
bool st[N];
LL dist1[N], dist2[N];
void add(int h[], int a, int b, int c)
{
w[idx] = c;
e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}
void dijkstra(int h[], LL dist[], int start)
{
memset(st, false, sizeof st);
memset(dist, 0x3f, sizeof dist1);
dist[start] = 0;
priority_queue<PII, vector<PII>, greater<PII> > q;
q.push({0, start});
while(q.size())
{
auto t = q.top();
q.pop();
int ver = t.y;
LL distance = t.x;
if(st[ver]) continue;
st[ver] = true;
for(int i = h[ver]; i != -1; i = ne[i])
{
int j = e[i];
if(dist[j] > distance + w[i])
{
dist[j] = distance + w[i];
q.push({dist[j], j});
}
}
}
}
int main()
{
ios::sync_with_stdio(false);
memset(h1, -1, sizeof h1);
memset(h2, -1, sizeof h2);
cin >> n >> m >> q;
while(m -- )
{
int a, b, c, d;
cin >> a >> b >> c >> d;
add(h1, a, b, c);
add(h2, b, a, d);
}
for(int i = 1; i <= n; i ++ ) cin >> radio[i];
dijkstra(h1, dist1, 1);
dijkstra(h2, dist2, n);
multiset<LL> s;
for(int i = 1; i <= n; i ++ )
{
if(dist1[i] == INF || dist2[i] == INF) continue;
//此时对于后面需要的代金券兑换的现金需要向上取整
s.insert(dist1[i] + ceil(1.0 * dist2[i] / radio[i]));
}
while(q -- )
{
int a, b;
cin >> a >> b;
if(dist1[a] != INF && dist2[a] != INF)
{
s.erase(s.find(dist1[a] + ceil(1.0 * dist2[a] / radio[a])));
radio[a] = b;
s.insert(dist1[a] + ceil(1.0 * dist2[a] / radio[a]));
}
cout << *s.begin() << endl;
}
return 0;
}
一个上述关于set注意点的例子
如果我们执行option1,那么set里面还有{1,10,110}
如果我们执行option2,那么set里面还有{1,110}
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <set>
using namespace std;
int main()
{
multiset<int> s;
s.insert(1);
s.insert(10);
s.insert(110);
s.insert(10);
// s.erase(s.find(10));//option1
s.erase(10);//option2
for(auto &x : s) cout << x << ' ';
return 0;
}