图论-Dijsktra算法学习笔记

Dijkstra 算法学习笔记

核心思想

Dijkstra 的核心思想就是用一个点看看能不能"绕路更近"

关键就是:

  1. 取点
  2. 遍历
  3. if(d[v] > d[u] + w)d[v] = d[u] + wv 点入队

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

我们可以建分层图,每条边有两种通向:

  1. 在每一层普通建边
  2. 指向下一层的点,注意是单向的,u -> 下一层vv -> 下一层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;
}
posted @ 2026-08-01 20:24  敛才有道  阅读(4)  评论(0)    收藏  举报