一、图的基础
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;
}