第八章-图论

一、图的基础

1.1 图的基础概念

1.2 DFS遍历图

1.3 BFS遍历图

1.4 两道例题

帮派弟位

link:1.帮派弟位 - 蓝桥云课

此题数据于 2026-02-02 加强,新增的测试点中有一个为根不是1的数

code:

复制代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll MAXN = 1e5 + 10;
ll n, m, dp[MAXN];
vector<ll> g[MAXN];

struct Node{
  ll idx, cnt;
} a[MAXN];

ll dfs(ll x)
{
  if(dp[x] != -1) return dp[x];
  ll ret = g[x].size();
  for(int i = 0; i < g[x].size(); i++) ret += dfs(g[x][i]);
  return dp[x] = ret;
}

int main()
{
  cin>>n>>m;
  for(int i = 1; i <= n - 1; i++)
  {
    ll l, r;cin>>l>>r;
    g[r].push_back(l);
  }
  memset(dp, -1, sizeof dp);
  for(int i = 1; i <= n; i++)
  {
    a[i] = {i, dfs(i)};
  }
  sort(a + 1, a + 1 + n, [](Node& a, Node& b){
    return a.cnt != b.cnt ? a.cnt > b.cnt : a.idx <b.idx;
    });
  ll ans = 0;
  for(int i = 1; i <= n; i++) 
  {
    if(a[i].idx == m) 
    {
      ans = i;
      break;
    }
  }
  cout<<ans<<endl;
  return 0;
}
可行路径的方案数

link:1.可行路径的方案数 - 蓝桥云课

错误code(不及时set vis 为true)

复制代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll MAXN = 2e5 + 10, p = 1e9 + 7;
ll n, m, ans = -1, cnt[MAXN];
vector<ll> g[MAXN];
bool vis[MAXN];


int main()
{
  cin>>n>>m;
  for(int i = 1; i <= m; i++)
  {
    ll a, b; cin>>a>>b;
    g[a].push_back(b);
    g[b].push_back(a);
  }

  queue<ll> q, q2;
  q.push(1); vis[1] = true; cnt[1] = 1;
  while(q.size())
  {
    q2 = {};
    while(q.size())
    {
      ll x = q.front(); q.pop();
      for(int i = 0; i < g[x].size(); i++)
      {
        if(vis[g[x][i]])continue;
        // 此时必须vis[g[x][i]]=true, 不然会导致同一节点重复入队
        cnt[g[x][i]] += cnt[x]; cnt[g[x][i]] %= p;
        q2.push(g[x][i]);
      }
    }
    while(q2.size())
    {
      ll x = q2.front(); q2.pop();
      vis[x] = true;
      q.push(x);
    }
  }
  cout<< cnt[n];
  return 0;
}

AC code

复制代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll MAXN = 2e5 + 10, p = 1e9 + 7;

ll n, m, d[MAXN], dp[MAXN];
vector<ll> g[MAXN];

int main() {
  cin >> n >> m;
  for(int i = 1; i <= m; i++) {
    ll a, b; cin >> a >> b;
    g[a].push_back(b);
    g[b].push_back(a);
  }
  
  memset(d, 0x3f, sizeof d);
  queue<ll> q;
  
  d[1] = 0;
  dp[1] = 1;
  q.push(1);
  
  while(q.size())
  {
    ll x = q.front(); q.pop();
    for(const auto& s:g[x])
    {
      if(d[x] + 1 < d[s]) 
      {
        d[s] = d[x] + 1;
        dp[s] = dp[x];
        q.push(s);
      }
      else if(d[x] + 1 == d[s]) dp[s] = (dp[s] + dp[x]) % p;
    }
  }
  
  cout << dp[n] << endl;
  return 0;
}

二、拓扑排序

什么是拓扑排序

拓扑排序算法

拓扑和动态规划结合

走多远(例题)

link:1.走多远 - 蓝桥云课

tips:

  • q.front()出来的out一定是前置条件都满足了, 所以用此时的out更新s是不会错的

code

复制代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll MAXN = 1e6 + 10;
ll n, m, dp[MAXN], ind[MAXN];
vector<ll> g[MAXN];

int main()
{
  cin>>n>>m;
  for(int i = 1; i <= m; i++)
  {
    ll u, v; cin>>u>>v;
    g[u].push_back(v); 
    ind[v]++;
  }

  // DAG-DP
  ll ans = 0;
  queue<ll> q;
  for(int i = 1; i <= n; i++) if(!ind[i]) q.push(i);
  while(q.size())
  {
    ll out = q.front(); q.pop();
    for(int s:g[out])
    {
      ind[s]--;
      if(!ind[s])q.push(s);
      dp[s] = max(dp[out] + 1, dp[s]);
      ans = max(dp[s], ans);
    }
  }
  cout<<ans<<endl;
  return 0;
}

分析:

三、最短路

Floyed (n <= 500)

算法实现
蓝桥公园(例题)

link:1.蓝桥公园 - 蓝桥云课

code

复制代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll MAXN = 405;
ll n, m, q, dp[MAXN][MAXN];

int main()
{
  memset(dp, 0x3f, sizeof dp);
  cin>>n>>m>>q;
  for(int i = 1; i <= n; i++) dp[i][i] = 0;// 不要忘了初始化
  for(int i = 1; i <= m; i++)
  { 
    ll u, v, w; cin>>u>>v>>w;
    dp[u][v] = min(dp[u][v], w);// 不要忘了取小,可能两点之间多条边
    dp[v][u] = min(dp[v][u], w);
  }
  // Floyed
  for(int k = 1; k <= n; k++)
    for(int i = 1; i <= n; i++)
      for(int j = 1; j <= n; j++)
        dp[i][j] = min(dp[i][j], dp[i][k] + dp[k][j]);

  while(q--)
  {
    ll st, ed; cin>>st>>ed;
    cout<<(dp[st][ed] == 0x3f3f3f3f3f3f3f3f ? -1 : dp[st][ed])<<endl;
  }
  return 0;
}
tips
  • Floyed适用范围:N <= 500
  • 边权可正可负
  • 不要忘了d[]初始化:dp[i][j] = (i==j?0:INF)
  • 输入边时d[u][v] = min(d[u][v], w), 因为可能两点间有不止一条边,所以一定要取小

Dijkstra (单源最短路)

算法实现

tips:

  • 朴素Dijikstra:图-最短路径-Dijkstra(迪杰斯特拉)算法_哔哩哔哩_bilibili
  • 以下代码都是优先队列优化后的Dijkstra算法,和朴素Dijkstra算法可能实现方式稍有不同
  • 但是核心思想都一样:每次取源点集合外的最近的点,确定此点到st的最近距离,并更新源点集合
  • 优先队列优化后,只有pop出的点才被确定到st的最短路径(因为其经过了优先队列的最短选择),而push进去的不一定是最短路径。
蓝桥王国

link:1.蓝桥王国 - 蓝桥云课

code

复制代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll MAXN = 3e5 + 10;
ll n, m, d[MAXN];

struct Node{
  ll y, w;

  bool operator < (const Node& b) const {  // 必须加 const
    return w > b.w;  // 小根堆:距离小的优先
  }
} ;
vector<Node> g[MAXN];

void Dijkstra(ll st)
{
  memset(d, 0x3f, sizeof d);
  bool vis[MAXN] = {0};
  priority_queue<Node> pq;
  d[st] = 0;
  pq.push({st, 0});// vis[st] 不能初始为true
  while(pq.size())
  {
    Node out = pq.top(); pq.pop();
    ll x = out.y;
    if(vis[x])continue;
    vis[x] = true;
    for(const auto&[y, dw]:g[x])// x 到 y 距离 dw
    {
      if(d[x] + dw < d[y])
      {
        d[y] = d[x] + dw;
        pq.push({y, d[y]});
      }
    }
  }
}

int main()
{
  cin>>n>>m;
  for(int i = 1; i <= m; i++) 
  {
    ll u, v, w;cin>>u>>v>>w;
    g[u].push_back({v, w});
  }
  Dijkstra(1);
  for(int i = 1; i <= n; i++)cout<<(d[i] == 0x3f3f3f3f3f3f3f3f ? -1 : d[i])<<" ";
  return 0;
}
tips:
  • 复杂度:NlogN,适用于稀疏图(边少点多)
  • 限制:点:1e5 , 边:1e5
  • 自定义的operator < 一定要两个const,一个修饰参数,一个修饰函数本身

Johnson

几种常见最短路算法
背景描述
johnson算法
例题:小e的公路

link:1.小e的公路 - 蓝桥云课

code

分析:

四、生成树

4.1 最小生成树

4.2 Kruskal

旅行售货员(模板题)

link:1.旅行销售员 - 蓝桥云课

code

复制代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll MAXN = 1e5 + 10;
ll t, n, m, pre[MAXN];

struct EDGE{
  ll u, v, w;
  bool operator < (const EDGE& b)const
  {
    return w < b.w;
  }
} ;

ll root(ll x)
{
  return pre[x] = (pre[x] == x ? x : root(pre[x]));
}

void merge(ll x, ll y)
{
  pre[root(x)] = root(y);
}

void solve()
{
  EDGE egs[MAXN];
  cin>>n>>m;
  for(int i = 1; i <= m; i++)
  {
    ll x ,y, c; cin>>x>>y>>c;
    egs[i] = {x, y, c};
  }
  sort(egs + 1, egs + 1 + m);
  // kruskal
  for(int i = 1; i <= m; i++) pre[i] = i;
  ll ans = 0;
  for(int i = 1; i <= m; i++)
  {
    ll x = egs[i].u, y = egs[i].v, c = egs[i].w;
    if(root(x) == root(y)) continue;
    merge(x, y);
    ans = max(ans, c);
  }
  cout<<ans<<endl;
}

int main()
{
  cin>>t;
  while(t--)
  {
    solve();
  }
  return 0;
}

4.3 Prim

tips:

  • 注意Prim和Dijkstra算法中d[]的区别
旅行售货员(模板题)

link:1.旅行销售员 - 蓝桥云课

code

tips:

  • 一定要在pop后,判断vis后,立即取ans=max(ans, d[x])

  • 如果最后再ans=*max_element(d+1, d+1+n)的话会WA!,因为此时d[i]已经不是理想d[i]了,这是个大坑。

    #include <bits/stdc++.h>
    using namespace std;
    using ll = long long;
    const ll MAXN = 1e5 + 10, inf = 0x3f3f3f3f3f3f3f3f;

    struct Edge{
    ll y, w;
    bool operator < (const Edge b)const{
    return w > b.w;
    }
    };

    void Prim()
    {
    ll n, m; cin>>n>>m;
    ll d[MAXN]; memset(d, 0x3f, sizeof d );
    vector<Edge> g[MAXN];
    bool vis[MAXN]; memset(vis, 0, sizeof(vis));
    for(int i = 1; i <= m; i++)
    {
    ll x, y, c; cin>>x>>y>>c;
    g[x].push_back({y, c});
    g[y].push_back({x, c});
    }
    priority_queue<Edge> pq;
    pq.push({1, d[1]=0});
    ll ans = 0;
    while(pq.size())
    {
    ll x = pq.top().y; pq.pop();
    if(vis[x])continue;
    vis[x]=true;
    ans = max(ans, d[x]);
    for(const auto[y, w]:g[x])
    {
    if(w < d[y]) pq.push({y, d[y]=w});
    }
    }
    cout<<ans<<endl;
    // ll tmp = 0; for(int i = 1; i <= n; i++)tmp = max(tmp, d[i]); d[i]在vis[i]后是会变得,而且是随机变化,所以不能这样最后选择ans
    // cout<<tmp<<endl;
    }

    int main()
    {
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    ll t; cin>>t;
    while(t--)
    {
    Prim();
    }
    return 0;
    }

相关推荐
汀、人工智能2 小时前
[特殊字符] 第93课:太平洋大西洋水流问题
数据结构·算法·数据库架构·图论·bfs·太平洋大西洋水流问题
小辉同志3 小时前
207. 课程表
c++·算法·力扣·图论
计算机安禾6 小时前
【数据结构与算法】第38篇:图论(二):深度优先搜索(DFS)与广度优先搜索(BFS)
数据结构·算法·矩阵·排序算法·深度优先·图论·宽度优先
汀、人工智能6 小时前
[特殊字符] 第56课:在排序数组中查找元素的首末位置
数据结构·算法·数据库架构·图论·bfs·在排序数组中查找元素的首末位置
汀、人工智能7 小时前
[特殊字符] 第72课:杨辉三角
数据结构·算法·数据库架构·图论·bfs·杨辉三角
LTphy8 小时前
深度优先搜索的三种模板
算法·深度优先·图论
汀、人工智能9 小时前
[特殊字符] 第94课:删除无效的括号
数据结构·算法·数据库架构·图论·bfs·删除无效的括号
计算机安禾10 小时前
【数据结构与算法】第37篇:图论(一):图的存储结构(邻接矩阵与邻接表)
数据结构·算法·链表·排序算法·深度优先·图论·visual studio code
Imxyk11 小时前
P9242 [蓝桥杯 2023 省 B] 接龙数列
c++·算法·图论