图论-Dijsktra算法学习笔记
Dijkstra 算法学习笔记
核心思想
Dijkstra 的核心思想就是用一个点看看能不能"绕路更近"
关键就是:
- 取点
- 遍历
if(d[v] > d[u] + w),d[v] = d[u] + w,v点入队
v 点入队的时候,必须得用 d[v] 值在前面进行排序:q.push({d[v], v})
当然起点也要入队 push({d[s], s})
所以也必须用到 priority_queue
这个优先队列不仅得是 pii 类型,还必须要用 greater 进行排序
最后强调,是 d[v] > d[u] + w,不要记混
priority_queue<pii, vector<pii>, greater<pii>> q; // 这个是优先队列
while(q.size())
{
int u = q.top().second; q.pop(); // 取出u点
if(vis[u]) continue; // 判vis
vis[u] = 1;
for(auto x : e[u])
{
int v = x.v, w = x.w;
if(d[v] > d[u] + w)
{
d[v] = d[u] + w;
q.push({d[v], v});
}
// 考点都在这句话里面,是 d[v] > d[u] + w,不要记混
}
}
变式一:最短路计数
🟡 P1144 最短路计数
链接:P1144 最短路计数
实际上就只是多了自环与重边,但都不会影响结果
if(d[v] > d[u] + 1) d[v] = d[u] + 1,cnt[v] = cnt[u],q.push({d[v], v});
else if(d[v] == d[u] + 1) cnt[v] = (cnt[v] + cnt[u]) % mo;
本来第二行写 if,但这会导致逻辑错误,第一行之后 d[v] 肯定 == d[u] + 1
以后注意不相干的条件要写 else if
变式二:分层图
🟢 P4568 [JLOI2011] 飞行路线
链接:P4568 飞行路线
这个建分层图很有思想性
题目说能有 k 次机会把 w = 0
我们可以建分层图,每条边有两种通向:
- 在每一层普通建边
- 指向下一层的点,注意是单向的,
u -> 下一层v,v -> 下一层u
这个比较常考,原本一个点为 i,第 k 层图就是 i + k * n
价值不高的题目
| 题目 | 说明 |
|---|---|
| P2939 | 只是 P4568 的双倍积分 |
| P1629 | 一来一回,要把题目读清楚 |
📝 一句话总结:Dijkstra 核心三要素——堆优化取最小点、
vis出队判重、d[v] > d[u] + w松弛。
代码在这里
P3371 / Dijkstra 模板
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
typedef pair<int,int> pii;
int n,m,s;
int d[N]; // [新增] d[i] = 起点s到i的最短距离
bool vis[N]; // [新增] vis[i] = true表示i的最短路已确定
struct edge{int v,w;}; // [新增] v=终点, w=边权
vector<edge>e[N]; // [新增] 邻接表存图
priority_queue<pii,vector<pii>,greater<pii> >q; // [新增] 小根堆,按距离排序
int main()
{
#ifndef ONLINE_JUDGE
freopen("c.in","r",stdin);
freopen("c.out","w",stdout);
#endif
// BEGIN!
cin>>n>>m>>s;
for(int i=1,u,v,w;i<=m;i++)
cin>>u>>v>>w,
e[u].push_back({v,w});
memset(d,0x3f,sizeof(d)); // [新增] 初始化为无穷大
d[s]=0;
q.push({0,s}); // [新增] 起点入队,first是距离,second是节点
while(q.size())
{
int u=q.top().second;q.pop();
if(vis[u]) continue; // [新增] 已确定最短路则跳过
vis[u]=1; // [新增] 标记已确定
for(auto x:e[u])
{
int v=x.v,w=x.w;
if(d[v]>d[u]+w) // [新增] 核心:松弛操作
{
d[v]=d[u]+w;
q.push({d[v],v});
}
}
}
for(int i=1;i<=n;i++) cout<<d[i]<<" ";
return 0;
}
P1144 最短路计数
// 开始修改!
// 日志,1716再次提交,WA,考虑重边问题
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
const int mo=1e5+3; // [新增] 答案对100003取模
typedef pair<int,int> pii;
int n,m;
int d[N];
int cnt[N]; // [新增] cnt[i] = 从1到i的最短路条数
int vis[N];
vector<int> e[N]; // [新增] 无权图,不需要存边权
priority_queue<pii,vector<pii>,greater<pii> >q;
// [新增] 堆里必须按照距离和节点,否则堆的顺序不是按距离排列的
int main()
{
#ifndef ONLINE_JUDGE
freopen("c.in","r",stdin);
// freopen("c.out","w",stdout);
#endif
// BEGIN!
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>n>>m;
for(int i=1,u,v;i<=m;i++)
cin>>u>>v,
e[u].push_back(v), // [新增] 无向图,建双向边
e[v].push_back(u);
memset(d,0x3f,sizeof(d));
d[1]=0; // [新增] 从1号点出发
cnt[1]=1; // [新增] 起点到自己的最短路条数为1
q.push({0,1});
while(q.size())
{
int u=q.top().second;q.pop();
if(vis[u]) continue;
vis[u]=1;
for(auto v:e[u])
{
if(d[v]>d[u]+1) // [新增] 找到更短路径
{
d[v]=d[u]+1;
cnt[v]=cnt[u]; // [新增] 第一次到达,覆盖
q.push({d[v],v});
}
else if(d[v]==d[u]+1) // [新增] 找到另一条同样短的路径
{
cnt[v]=(cnt[v]+cnt[u])%mo; // [新增] 累加
}
}
}
for(int i=1;i<=n;i++) cout<<cnt[i]<<endl;
return 0;
}
P1629 邮递员送信
// [新增] 题目大意:单向图,邮递员从1出发到每个点送信,然后返回1,求总路程
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
typedef pair<int,int> pii;
struct edge{int v,w;};
vector<edge>e[N]; // [新增] 正向图
int n,m;
int d[N];
bool vis[N];
long long sum; // [新增] 答案可能很大,用long long
void dijsktra(int s)
{
memset(d,0x3f,sizeof(d));
memset(vis,0,sizeof(vis));
priority_queue<pii,vector<pii>,greater<pii> >q;
d[s]=0,q.push({0,s});
while(q.size())
{
int u=q.top().second;q.pop();
if(vis[u]) continue;
vis[u]=1;
for(auto x:e[u])
{
int v=x.v,w=x.w;
if(d[v]>d[u]+w) d[v]=d[u]+w,q.push({d[v],v});
}
}
}
int go,back; // [新增] 调试变量,记录去程和回程
int main()
{
#ifndef ONLINE_JUDGE
freopen("c.in","r",stdin);
freopen("c.out","w",stdout);
#endif
// BEGIN!
cin>>n>>m;
for(int i=1,u,v,w;i<=m;i++)
{
cin>>u>>v>>w;
e[u].push_back({v,w}); // [新增] 有向图,只存一个方向
}
// [新增] 第一次:从1到所有点(去程)
dijsktra(1);
for(int i=1;i<=n;i++) sum+=d[i],go+=d[i];
// [新增] 第二次:从所有点到1(回程)
// [新增] 对每个点跑Dijkstra求到1的距离
for(int i=1;i<=n;i++) dijsktra(i),sum+=d[1],back+=d[1];
cout<<sum<<endl;
// printf("go:%d\nback:%d\n",go,back);
return 0;
}
P4568 飞行路线
#include<bits/stdc++.h>
using namespace std;
const int N=3e5+5; // [新增] n*k = 10000*10 = 100000,开3e5安全
typedef pair<int,int> pii;
struct edge{int v,w;};
vector<edge>e[N];
int d[N];
bool vis[N];
int n,m,k,s,t;
int ans=INT_MAX;
void dijsktra(int s)
{
memset(d,0x3f,sizeof(d));
memset(vis,0,sizeof(vis));
priority_queue<pii,vector<pii>,greater<pii> >q;
q.push({0,s}),d[s]=0;
while(q.size())
{
int u=q.top().second;q.pop();
if(vis[u]) continue;
vis[u]=1;
for(auto x:e[u])
{
int v=x.v,w=x.w;
if(d[v]>d[u]+w)
{
d[v]=d[u]+w;
q.push({d[v],v});
}
}
}
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("c.in","r",stdin);
freopen("c.out","w",stdout);
#endif
// BEGIN!
cin>>n>>m>>k>>s>>t;
for(int i=1,u,v,w;i<=m;i++)
{
cin>>u>>v>>w;
// [新增] 建分层图:共k+1层(0~k)
for(int j=0;j<=k;j++)
{
// [新增] 同层正常连边
e[u+n*j].push_back({v+n*j,w});
e[v+n*j].push_back({u+n*j,w});
// [新增] 层间边:用一次免费机会,从j层到j+1层
if(j!=k)
{
e[u+n*j].push_back({v+(j+1)*n,0});
e[v+n*j].push_back({u+(j+1)*n,0});
}
}
}
dijsktra(s);
// [新增] 取所有层中终点t的最小距离
for(int j=0;j<=k;j++)
ans=min(ans,d[j*n+t]);
cout<<ans;
return 0;
}
P2939 Revamping Trails
#include<bits/stdc++.h>
using namespace std;
const int N=3e5+5; // [新增] 与P4568同款分层图
typedef pair<int,int> pii;
struct edge{int v,w;};
vector<edge>e[N];
int d[N];
bool vis[N];
int n,m,k;
int ans=INT_MAX;
void dijsktra(int s)
{
memset(d,0x3f,sizeof(d));
memset(vis,0,sizeof(vis));
priority_queue<pii,vector<pii>,greater<pii> >q;
q.push({0,s}),d[s]=0;
while(q.size())
{
int u=q.top().second;q.pop();
if(vis[u]) continue;
vis[u]=1;
for(auto x:e[u])
{
int v=x.v,w=x.w;
if(d[v]>d[u]+w)
{
d[v]=d[u]+w;
q.push({d[v],v});
}
}
}
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("c.in","r",stdin);
freopen("c.out","w",stdout);
#endif
// BEGIN!
cin>>n>>m>>k;
for(int i=1,u,v,w;i<=m;i++)
{
cin>>u>>v>>w;
// [新增] 与P4568完全相同的分层图建法
for(int j=0;j<=k;j++)
{
e[u+n*j].push_back({v+n*j,w});
e[v+n*j].push_back({u+n*j,w});
if(j!=k)
{
e[u+n*j].push_back({v+(j+1)*n,0});
e[v+n*j].push_back({u+(j+1)*n,0});
}
}
}
dijsktra(1); // [新增] 起点固定为1
// [新增] 终点为n,取所有层的最小值
for(int j=0;j<=k;j++)
ans=min(ans,d[j*n+n]);
cout<<ans;
return 0;
}

浙公网安备 33010602011771号