单源最短路径刷题记
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;
}
以上是关于单源最短路径刷题记的主要内容,如果未能解决你的问题,请参考以下文章