单源最短路径刷题记

Posted lyfoi

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了单源最短路径刷题记相关的知识,希望对你有一定的参考价值。

Codeforces Alpha Round #20 (Codeforces format) C Dijkstra?

翻译

给出一张图,请输出其中任意一条可行的从点 (1) 到点 (n) 的最短路径。

思路

板子题。

Code

#include<queue>
#include<iostream>
#include<vector>
#include<set>
#include<cstring> 
#include<algorithm>
using namespace std;
const long long INF=1e18;
vector<pair<int,int>>edge[100001];
set<pair<int,int>>q;
long long d[100001];
int pr[100001];
bool vis[100001];
int n,m;
int main()
{
    cin>>n>>m;
    fill(d,d+n,INF);
    fill(pr,pr+n,-1);
    for(int i=0;i<m;i++)
    {
        int x,y,v;
        cin>>x>>y>>v;
        x--,y--;
        edge[x].push_back({y,v});
        edge[y].push_back({x,v});
    }
    d[0]=0;
    q.insert({0,0});
    while(!q.empty())
    {
        int u=q.begin()->second;
        q.erase(q.begin());
        for(auto p:edge[u])
        {
            int e=p.first;
            int w=p.second;
            if(d[u]+w<d[e])
            {
                q.erase({d[e],e});
                d[e]=d[u]+w;
                pr[e]=u;
                q.insert({d[e],e});
            }
        }
    }
    if(d[n-1]==INF)
        cout<<-1<<endl;
    else{
        vector<int>ans;
        for(int i=n-1;i!=-1;i=pr[i])
            ans.push_back(i);
        reverse(ans.begin(),ans.end());
        for(int i:ans)
            cout<<i+1<<" ";
        cout<<endl;
    }
    return 0;
}

Codeforces Round #142 (Div. 1) B Planets

翻译

在宇宙里有 (n) 个星球,分别编号为 (1,2,...,n) 。Jack现在在 (1) 号星球上,他要去 (n) 号星球。已知一些星球之间有双向的传送通道,(Jack)可以通过这些传送通道移动。每次传送需要一些时间,在不同的星球之间传送也可能需要不同时间。

当有其他人在使用这个星球的传送通道时,Jack无法离开这个星球。比如,如果有人在 (t) 时刻使用通道,那Jack只能在 (t+1) 时刻离开(如果(t+1)时刻没有人在使用通道)。

现在,(Jack)想请你计算他最早可以在哪个时刻到达 (n) 号星球。(Jack)(0)时刻出发。

思路

一道近乎裸的单源最短路径卡到了许多红名(这次比赛的第一个提交的红名大佬(WA)了,过了十几分钟才改对)

最朴素最暴力最不容易错的方法是用一个(set)来记录每个点的人是什么时间到的,于是就直接:

while(book[u].count(t)) t++;

不会超时,常数感人。

坑点

  • 第一个提交死的地方

注意:如果你到了终点是不需要等待的!

  • 第一个提交第二个死的地方

最大值开的太小了,1e9不够

  • 许多集训队大佬死的地方

没有这句话:

if(t!=d[u])
    continue;

Code

#include<bits/stdc++.h>
using namespace std;
const long long INF=1<<30;
set<long long>book[100001];
vector <pair<long long,long long>> edge[100001];
long long d[100001];
long long n,m;
priority_queue <pair<long long,long long>> q;
int main()
{
    cin>>n>>m;
    fill(d,d+n,INF);
    for(long long i=0;i<m;i++)
    {
        long long x,y,w;
        cin>>x>>y>>w; x--,y--;
        edge[x].push_back({y,w});
        edge[y].push_back({x,w});
    }
    for(long long i=0;i<n;i++)
    {
        long long k; cin>>k;
        while(k--)
        {
            long long t; cin>>t;
            book[i].insert(t);
        }
    }
    d[0]=0;
    q.push({0,0});
    while(!q.empty())
    {
        long long u=q.top().second;
        long long t=-q.top().first;
        q.pop();
        if(t!=d[u])
            continue;
        while(book[u].count(t)) t++;
        for(long long i=0;i<edge[u].size();i++)
        {
            long long v=edge[u][i].first;
            long long w=edge[u][i].second;
            if(w+t<d[v])
            {
                d[v]=t+w;
                q.push({-d[v],v});
            }
        }
    }
    if(d[n-1]==INF)
        cout<<-1<<endl;
    else
        cout<<d[n-1]<<endl;
    return 0;
}

Codeforces Round #290 (Div. 2) D Fox And Jumping

翻译

给出 (n) 张卡片,分别有 (l_i)(c_i)。在一条无限长的纸带上,你可以选择花 (c_i) 的钱来购买卡片 (i),从此以后可以向左或向右跳 (l_i) 个单位。问你至少花多少元钱才能够跳到纸带上全部位置。若不行,输出 (-1)

思路

分析该问题,先考虑两个数的情况,发现想要跳到每一个格子上,必须使得这些数通过数次相加或相加得出的绝对值为 (1),进而想到了裴蜀定理。

可以推出:如果 (a)(b) 互质,那么一定存在两个整数 (x)(y),使得 (ax+by=1).

由此得出了若选择的卡牌的数通过数次相加或相减得出的绝对值为 (1) ,那么这些数一定互质,此时可以考虑动态规划求解(PS:动态规划这个正解竟然没最短路快!)。

不过可以转移思想,因为这些数互质,即为 (0) 号节点开始,每走一步求 (gcd)(节点号, 下一个节点),同时记录代价,就成为了从 (0) 通过不断 (gcd) 最后变为 (1) 的最小代价。

由于:互质即为最大公因数为 (1)(gcd(0,x)=x) 这两个定理,可以证明该算法的正确。选择优先队列优化 Dijkstra 求解。

不过还有个问题,即为需要记录是否已经买过一个卡片,开数组标记由于数据范围达到(10^9)会超出内存限制,可以想到使用 unordered_map (比普通的 map 更快地访问各个元素,迭代效率较低,可以看我写的关联容器那篇文章。

Code

#include<bits/stdc++.h>
using namespace std;
int n,t[300],k[300];
map<int,int>mp;
priority_queue<pair<int,int>>q;
void add(int g,int k)
{
    if(mp.find(g)==mp.end()||mp[g]>k)
    {
        mp[g]=k;
        q.push({-k,g});
    }
}
int main()
{
    scanf("%d",&n);
    for(int i=0;i<n;i++) scanf("%d",&t[i]);
    for(int i=0;i<n;i++) scanf("%d",&k[i]);
    add(0,1);
    while(!q.empty())
    {
        int u=q.top().second;
        int w=-q.top().first;
        q.pop();
        if(mp[u]!=w)  continue;
        if(u==1) return printf("%d
",w-1),0;
        for(int i=0;i<n;i++) add(__gcd(u,t[i]),k[i]+w);
    }
    puts("-1");
    return 0;
}

以上是关于单源最短路径刷题记的主要内容,如果未能解决你的问题,请参考以下文章

单源最短路径Dijkstra算法的思想详细步骤代码

Bellman-ford 单源最短路径算法

贪心算法—单源最短路径

单源最短路径 djkstra

洛谷 P3371 单源最短路径(Java版)

算法之单源最短路径