最短路跑两遍

 题目详情 - L3-1 森森旅游 (30 分) (pintia.cn)

参考思路:

由题意得每次兑换旅行币都要将现金兑完,所以可以将拆解成两段不同的最短路,一条是从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。

一些需要注意的点

  1. 由于本题需要建两个图,所以边数要开两倍,之前点数不变过了,但隔了一段时间提交Wrong了,开了两倍点数又过了,说明点数也是要开两倍的。。
  2. 由于本题的val值很大,1e9,所以要开longlong,很多地方要换成longlong,容易忽视
  3. 关于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;
}

posted @ 2022-05-05 08:41  光風霽月  阅读(12)  评论(0编辑  收藏  举报