图 { 图的定义 图结构的存储 { 邻接矩阵法、邻接表法 邻接多重表、十字链表 图的遍历 { 深度优先遍历 广度优先遍历 图的相关应用 { 最小生成树: P r i m 算法、 K r u s k a l 算法 最短路径: D i j k s t r a 算法、 F l o y d 算法 拓扑排序: A O V 网 关键路径: A O E 网 图\left\{ \begin{array}{l} 图的定义\\ 图结构的存储\left\{ \begin{array}{l} 邻接矩阵法、邻接表法\\ 邻接多重表、十字链表 \end{array} \right.\\ 图的遍历\left\{ \begin{array}{l} 深度优先遍历\\ 广度优先遍历 \end{array} \right.\\ 图的相关应用\left\{ \begin{array}{l} 最小生成树:Prim算法、Kruskal算法\\ 最短路径:Dijkstra算法、Floyd算法\\ 拓扑排序:AOV网\\ 关键路径:AOE网 \end{array} \right. \end{array} \right. 图⎩ ⎨ ⎧图的定义图结构的存储{邻接矩阵法、邻接表法邻接多重表、十字链表图的遍历{深度优先遍历广度优先遍历图的相关应用⎩ ⎨ ⎧最小生成树:Prim算法、Kruskal算法最短路径:Dijkstra算法、Floyd算法拓扑排序:AOV网关键路径:AOE网
文章目录
-
- [6.1 图的基本概念](#6.1 图的基本概念)
-
-
- [例一 Superb Graphs](#例一 Superb Graphs)
-
- [6.2 图的存储及基本操作](#6.2 图的存储及基本操作)
-
- [6.2.1 邻接矩阵法](#6.2.1 邻接矩阵法)
- [6.2.2 邻接表法](#6.2.2 邻接表法)
-
- [例二 精神胜利](#例二 精神胜利)
- [6.3 图的遍历](#6.3 图的遍历)
-
- [6.3.1 广度优先搜索](#6.3.1 广度优先搜索)
-
- [例三 力的平衡](#例三 力的平衡)
- [6.3.2 深度优先搜索](#6.3.2 深度优先搜索)
-
- [例四 特立独行的幸福](#例四 特立独行的幸福)
- [6.3.3 图的遍历与图的连通性](#6.3.3 图的遍历与图的连通性)
- [6.3.4 拓展](#6.3.4 拓展)
-
- [例五 Even String](#例五 Even String)
- [6.4 图的应用](#6.4 图的应用)
-
- [6.4.1 最小生成树](#6.4.1 最小生成树)
-
- [例六 严格次小生成树](#例六 严格次小生成树)
- [6.4.2 最短路径](#6.4.2 最短路径)
-
- [例七 垃圾箱分布](#例七 垃圾箱分布)
- [例八 传送门](#例八 传送门)
- [例九 【模板】差分约束](#例九 【模板】差分约束)
- [例十 影响力](#例十 影响力)
- [6.4.3 有向无环图描述表达式](#6.4.3 有向无环图描述表达式)
-
- [例十一 计算图](#例十一 计算图)
- [6.4.4 拓扑排序](#6.4.4 拓扑排序)
-
- [例十二 千手观音](#例十二 千手观音)
- [例十三 Follow the Penguins](#例十三 Follow the Penguins)
- [6.4.5 关键路径](#6.4.5 关键路径)
-
- [例十四 Commemorative Race](#例十四 Commemorative Race)
- 参考文献
6.1 图的基本概念
例一 Superb Graphs
给定 k k k 个无向图 G 1 , G 2 , ... , G k G_1, G_2, ..., G_k G1,G2,...,Gk。每个图均有 n n n 个顶点,标号从 1 1 1 到 n n n。
我们称图 H H H 是图 G G G 的"superb graph",当且仅当 H H H 的顶点集可划分为两两不交的子集 S 1 , S 2 , ... , S n S_1, S_2, ..., S_n S1,S2,...,Sn(每个子集对应 G G G 的一个顶点),满足:
- 对每个 i i i, S i S_i Si 在 H H H 中要么是独立集 (内部无边),要么是完全子图(内部全连边);
- 对 G G G 中任意两不同顶点 u u u、 v v v, G G G 中 u u u 与 v v v 有边当且仅当 H H H 中 S u S_u Su 与 S v S_v Sv 完全连接( S u S_u Su 内每点与 S v S_v Sv 内每点均有边)。
此外,需存在 k k k 个"superb graph" H 1 , ... , H k H_1,...,H_k H1,...,Hk(分别对应 G 1 , ... , G k G_1,...,G_k G1,...,Gk )及每个 H i H_i Hi 的顶点标号(对应 G i G_i Gi 原顶点),使得:
对任意原始顶点 v v v,设其在 H i H_i Hi 中对应子集为 S v ( i ) S_v^{(i)} Sv(i),若某个 i i i 中 S v ( i ) S_v^{(i)} Sv(i) 是大小 > 1 >1 >1 的独立集,则所有 j ≠ i j≠i j=i 中 S v ( i ) S_v^{(i)} Sv(i) 均不能是大小 > 1 >1 >1 的完全子图。
判断是否存在这样的 H 1 , ... , H k H_1,...,H_k H1,...,Hk。存在则输出"Yes",否则输出"No"。
独立集 :对于一个图 G G G ,如果 S S S 中没有两个顶点用边相连,那么顶点的子集 S S S 称为独立集。
完全子图 :对于图 G G G 来说,如果 S S S 的每个顶点都与 S S S 的其他顶点以边相连,那么顶点子集 S S S 称为一个小群。
c
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 200020, M = 400010;
ll T;
ll n, m, k;
ll h[N], e[M], ne[M], idx;
ll dfn[N], low[N], timestamp;
ll stk[N], top;
bool in_stk[N];
ll id[N], scc_cnt;
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
void tarjan(int u)
{
dfn[u] = low[u] = ++timestamp;
stk[++top] = u, in_stk[u] = true;
for (int i = h[u]; ~i; i = ne[i])
{
int j = e[i];
if (!dfn[j])
{
tarjan(j);
low[u] = min(low[u], low[j]);
}
else if (in_stk[j])
low[u] = min(low[u], dfn[j]);
}
if (dfn[u] == low[u])
{
int y;
++scc_cnt;
do {
y = stk[top--];
in_stk[y] = false;
id[y] = scc_cnt;
} while (y != u);
}
}
void solve()
{
cin >> n >> k;
memset(h, -1, (2 * n + 10) * sizeof (ll));
memset(dfn, 0, (2 * n + 10) * sizeof (ll));
memset(id, 0, (2 * n + 10) * sizeof (ll));
scc_cnt = timestamp = top = idx = 0;
while (k--)
{
vector<vector<ll>> a(n + 10);
map<vector<ll>, vector<ll>> mp;
cin >> m;
while (m--)
{
int x, y;
cin >> x >> y;
x--, y--;
a[x].push_back(y);
a[y].push_back(x);
}
for (int i = 0; i < n; i++)
{
vector<ll> tmp = a[i];
tmp.push_back(i);
sort(tmp.begin(), tmp.end());
mp[tmp].push_back(i);
}
// cout << "independent set" << endl;
for (auto [x, y] : mp)
for (int i = 0; i < y.size(); i++)
for (int j = i + 1; j < y.size(); j++)
{
// cout << y[i] << " " << y[j] << endl;
add(2 * y[i] + 1, 2 * y[j]);
add(2 * y[j] + 1, 2 * y[i]);
}
mp.clear();
for (int i = 0; i < n; i++)
{
sort(a[i].begin(), a[i].end());
mp[a[i]].push_back(i);
}
// cout << "clique" << endl;
for (auto [x, y] : mp)
for (int i = 0; i < y.size(); i++)
for (int j = i + 1; j < y.size(); j++)
{
// cout << y[i] << " " << y[j] << endl;
add(2 * y[i], 2 * y[j] + 1);
add(2 * y[j], 2 * y[i] + 1);
}
}
for (int i = 0; i < 2 * n; i++)
if (!dfn[i])
tarjan(i);
for (int i = 0; i < n; i++)
if (id[2 * i] == id[2 * i + 1])
{
cout << "No" << endl;
return;
}
cout << "Yes" << endl;
return;
}
int main()
{
cin.tie(0);
ios::sync_with_stdio(false);
cin >> T;
while (T--) solve();
return 0;
}
6.2 图的存储及基本操作
6.2.1 邻接矩阵法
c
#define MaxVertexNum 100 // 顶点数目的最大值
typedef char VertexType; // 顶点对应的数据类型
typedef int EdgeType; // 边对应的数据类型
typedef struct {
VertexType vex[MaxVertexNum]; // 顶点表
EdgeType edge[MaxVertexNum][MaxVertexNum]; // 邻接矩阵,边表
int vexnum, arcnum; // 图的当前顶点数和边数
} MGraph;
6.2.2 邻接表法
c
#define MaxVertexNum 100 // 图中顶点数目的最大值
typedef struct ArcNode { // 边表结点
int adjvex; // 该弧所指向的顶点的位置
struct ArcNode * nextarc; // 指向下一条弧的指针
// InfoType info; // 网的边权值
} ArcNode;
typedef struct VNode { // 顶点表结点
VertexType data; // 顶点信息
ArcNode * firstarc; // 指向第一条依附该顶点的弧的指针
} VNode, AdjList[MaxVertexNum];
typedef struct {
AdjList vertices; // 邻接表
int vexnum, arcnum; // 图的顶点数和弧数
} ALGraph; // ALGraph是以邻接表存储的图类型
例二 精神胜利
c
#include <bits/stdc++.h>
using namespace std;
using PII = pair<long long, long long>;
typedef long long ll;
#define x first
#define y second
const int N = 5010, M = 12500010;
ll a[N][N];
ll n, q;
string s[N];
ll h[N], e[M], ne[M], idx;
bool st[N];
map<PII, ll> mp;
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
ll bfs(ll u, ll b)
{
if (mp.count({u, b})) return mp[{u, b}];
deque<PII> q;
q.push_back({u, 0});
st[u] = true;
while (q.size())
{
auto t = q.front();
q.pop_front();
ll ver = t.x;
for (int i = h[ver]; ~i; i = ne[i])
{
int j = e[i];
if (st[j]) continue;
if (j == b) return t.y;
mp[{u, j}] = t.y;
mp[{ver, j}] = 0;
q.push_back({j, t.y + 1});
st[j] = true;
}
}
return -1;
}
int main()
{
cin.tie(0);
ios::sync_with_stdio(false);
cin >> n >> q;
if (n > 500)
{
for (int i = 1; i < n; i++) cin >> s[i];
while (q--)
{
ll a, b;
cin >> a >> b;
if (a < b && s[a][b - a - 1] == '1') cout << 0 << endl;
else if (a > b && s[b][a - b - 1] == '0') cout << 0 << endl;
else cout << 1 << endl;
}
return 0;
}
memset(h, -1, sizeof h);
for (int i = 1; i < n; i++)
{
cin >> s[i];
for (int j = 0; j < s[i].size(); j++)
if (s[i][j] == '1')
add(i, i + j + 1);
else add(i + j + 1, i);
}
while (q--)
{
ll a, b;
cin >> a >> b;
memset(st, 0, sizeof st);
cout << bfs(a, b) << endl;
}
return 0;
}
6.3 图的遍历
6.3.1 广度优先搜索
c
bool visited[MaxVertexNum]; // 访问标记数组
void BFSTraverse(Graph G) { // 对图G进行广度优先遍历
for (int i = 0; i < G.vexnum; i++)
visited[i] = false; // 访问标记数组初始化
InitQueue(Q); // 初始化辅助队列Q
for (int i = 0; i < G.vexnum; i++) // 从0号顶点开始遍历
if (!visited[i]) // 对每个连通分量调用一次BFS()
BFS(G, i); // 若vi未访问过,从vi开始调用BFS()
}
void BFS(ALGraph G, int i) {
visit(i); // 访问初始顶点i
visited[i] = true; // 对i做已访问标记
EnQueue(Q, i); // 顶点i入队
while (!QueueEmpty(Q)) {
DeQueue(Q, v); // 队首顶点v出队
for (p = G.vertices[v].firstarc; p; p = p->nextarc) { // 检测v的所有邻接点
w = p->adjvex;
if (visited[w] == false) {
visit(w); // w为v的尚未访问的邻接点,访问w
visited[w] = true; // 对w做已访问标记
EnQueue(Q, w); // 顶点w入队
}
}
}
}
void BFS(MGraph G, int i) {
visit(i); // 访问初始顶点i
visited[i] = true; // 对i做已访问标记
EnQueue(Q, i); // 顶点i入队
while (!QueueEmpty(Q)) {
DeQueue(Q, v); // 队首顶点v出队
for (int w = 0; w < G.vexnum; w++) // 检测v的所有邻接点
if (visited[w] == false && G.edge[v][w] == 1) {
visit(w); // w为v的尚未访问的邻接点,访问w
visited[w] = true; // 对w做已访问标记
EnQueue(Q, w); // 顶点w入队
}
}
}
BFS算法 求解单源最短路径问题
c
void BFS_MIN_Distance(Graph G, int u) {
// d[i]表示从u到i的最短路径
for (int i = 0; i < G.vexnum; i++)
d[i] = INF; // 初始化路径长度
visited[u] = true; d[u] = 0;
EnQueue(Q, u);
while (!QueueEmpty(Q)) { // BFS算法主过程
DeQueue(Q, u); // 队头元素u出队
for (w = FirstNeighbor(G, u); w >= 0; w = NextNeighbor(G, u, w))
if (!visited[w]) { // w为u的尚未访问的邻接顶点
visited[w] = true; // 设已访问标记
d[w] = d[u] + 1; // 路径长度加1
EnQueue(Q, w); // 顶点w入队
}
}
}
例三 力的平衡
【题目描述】
在物理实验室中,Alice 正在研究力的平衡问题。
实验室配备了 k k k 种力发生器,第 i i i 种发生器可以产生力向量 ( F x i , F y i F_{x_i},F_{y_i} Fxi,Fyi) 牛顿。其中,正值表示向右/向上的力,负值表示向左/向下的力。
为了达到完美的力学平衡状态,Alice 需要选择若干个力发生器(每种可以使用任意多次),使得所有力的合力恰好为零向量 ( 0 , 0 0,0 0,0)。
由于实验经费有限,每次启动力发生器都需要消耗电能。Alice 希望启动力发生器的次数尽量少来达到平衡。
注意:Alice 必须至少启动一次力发生器,即答案至少为 1 1 1。
请帮助 Alice 计算最少需要启动多少次力发生器。如果无论如何都无法达到平衡,输出 − 1 −1 −1。
【输入】
第一行一个整数 t t t,表示测试数据的组数。
对于每组测试数据:
- 第一行一个整数 k k k,表示力发生器的种类数。
- 接下来 k k k 行,每行两个整数 F x i , F y i F_{x_i},F_{y_i} Fxi,Fyi,表示第 i i i 种力发生器产生的力向量。
对于所有测试数据:
- 1 ≤ t ≤ 10 1 \leq t \leq 10 1≤t≤10
- 1 ≤ k ≤ 500 1 \leq k \leq 500 1≤k≤500
- − 100 ≤ F x i , F y i ≤ 100 -100 \leq F_{x_{i}}, F_{y_{i}} \leq 100 −100≤Fxi,Fyi≤100
- 保证 ∑ k ≤ 1000 \sum k \leq 1000 ∑k≤1000
【输出】
对于每组测试数据,输出一行一个整数,表示最少需要启动的力发生器次数。如果无法达到平衡,输出 − 1 −1 −1。
【题解】
二维 Steinitz 引理 给出: 若一组二维向量的和为 0 0 0 ,且每个向量都落在关于原点中心对称的凸集 B B B 中, 那么存在一种重排,使得所有前缀和都落在 2 B 2B 2B 中。
图论建模
既然最优解的所有前缀和都不会离开 [ − 200 , 200 ] 2 [-200,200]^{2} [−200,200]2 ,就可以在这个有限网格上建图。
-
一个状态表示当前向量和 ( x , y ) (x, y) (x,y)
-
从 ( x , y ) (x, y) (x,y) 选择一个向量 F x i , F y i F_{x_i},F_{y_i} Fxi,Fyi ,转移到 x + F x i , y + F y i x + F_{x_i}, y + F_{y_i} x+Fxi,y+Fyi
-
只保留仍在 [ − 200 , 200 ] 2 [-200,200]^{2} [−200,200]2 内的转移
于是问题变成:
在这个有限图中,求从 ( 0 , 0 ) (0, 0) (0,0) 出发、经过至少一条边后再次回到 ( 0 , 0 ) (0, 0) (0,0) 的最短路长度。
由于每次转移代价都为 ,直接 BFS 即可。
c
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<ll, ll> PII;
#define x first
#define y second
const int N = 510, M = N * 2, off = 210, INF = 0x3f3f3f3f;
ll n;
PII F[N];
ll dist[N][N];
ll bfs()
{
queue<PII> q;
q.push({0, 0});
dist[0 + off][0 + off] = 0;
while (q.size())
{
auto [x, y] = q.front();
q.pop();
for (int i = 1; i <= n; i++)
{
int u = x + F[i].x, v = y + F[i].y;
if (u == 0 && v == 0) return dist[x + off][y + off] + 1;
if (abs(u) > 200 || abs(v) > 200) continue;
if (dist[u + off][v + off] < INF) continue;
dist[u + off][v + off] = dist[x + off][y + off] + 1;
q.push({u, v});
}
}
return -1;
}
void solve()
{
cin >> n;
for (int i = 1; i <= n; i++) cin >> F[i].x >> F[i].y;
for (int i = -200; i <= 200; i++)
for (int j = -200; j <= 200; j++)
dist[i + off][j + off] = INF;
cout << bfs() << endl;
}
int main()
{
cin.tie(0);
ios::sync_with_stdio(false);
ll Case = 1;
cin >> Case;
while (Case --> 0) solve();
return 0;
}
6.3.2 深度优先搜索
c
bool visited[MaxVertexNum]; // 访问标记数组
void DFSTraverse(Graph G) { // 对图G进行深度优先遍历
for (int i = 0; i < G.vexnum; i++)
visited[i] = false; // 初始化已访问标记数组
for (int i = 0; i < G.vexnum; i++) // 本代码从v0开始遍历
if (!visited[i]) // 对尚未访问的顶点调用DFS()
DFS(G, i);
}
void DFS(ALGraph G, int i) {
visit(i); // 访问初始顶点i
visited[i] = true; // 对i做已访问标记
for (p = G.vertices[i].firstarc; p; p = p->nextarc) { // 检测i的所有邻接点
int j = p->adjvex;
if (visited[j] == false)
DFS(G, j); // j为i的尚未访问的邻接点,递归访问j
}
}
void DFS(MGraph G, int i) {
visit(i); // 访问初始顶点i
visited[i] = true; // 对i做已访问标记
for (int j = 0; j < G.vexnum; j++) { // 检测i的所有邻接点
if (visited[j] == false && G.edge[i][j] == 1)
DFS(G, j); // j为i的尚未访问的邻接点,递归访问j
}
}
例四 特立独行的幸福
L2-029 特立独行的幸福 - 团体程序设计天梯赛-练习集
c
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 10010;
ll IsPrime(ll num)
{
for (int i = 2; i * i <= num; i++)
if (num % i == 0)
return 1;
return 2;
}
ll shift(ll num)
{
ll ret = 0;
while (num) ret += (num % 10) * (num % 10), num /= 10;
return ret;
}
ll a, b;
bool st[N];
pair<bool, ll> res[N];
ll flag;
pair<bool, ll> dfs(ll u)
{
ll v = shift(u);
if (v == 1) return res[u] = {true, 1};
if (st[v]) return res[u] = {res[v].first, res[v].second + 1};
st[v] = true;
res[u] = dfs(v);
res[u].second++;
return res[u];
}
void solve()
{
cin >> a >> b;
for (int i = a; i <= b; i++)
if (!st[i])
dfs(i);
bool flag = false;
for (int i = a; i <= b; i++)
if (res[i].first && !st[i])
{
flag = true;
cout << i << ' ' << res[i].second * IsPrime(i) << endl;
}
if (!flag) cout << "SAD" << endl;
}
int main()
{
cin.tie(0);
ios::sync_with_stdio(false);
ll Case = 1;
while (Case --> 0) solve();
return 0;
}
6.3.3 图的遍历与图的连通性
图的遍历算法可用于判断图的连通性。
6.3.4 拓展
例五 Even String
双向广搜
【题目大意】 您希望构造一个由小写拉丁字母组成的字符串 s s s ,使得以下条件成立:
- 对于每一对 i i i 和 j j j 这样的索引 s i = s j s_i=s_j si=sj ,这些索引的差值都是偶数,即 ∣ i − j ∣ m o d 2 = 0 |i−j|mod2=0 ∣i−j∣mod2=0 。
构造任何字符串都非常简单,因此我们将给出一个由 26 26 26 个数字组成的数组 c c c - 即字符串 s s s 中每个字母出现的所需次数。因此,每 i ∈ [ 1 , 26 ] i∈[1,26] i∈[1,26] 个拉丁字母中的 i i i 个字母应该出现 c i c_i ci 次。
你的任务是计算满足所有这些条件的不同字符串 s s s 的数量。由于答案可能很大,所以请输出它的模数 998244353 998244353 998244353 。
【题解视频连接】 Even String
c
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<ll, ll> PII;
typedef pair<PII, int> PIII;
#define MOD 998244353
ll T;
ll n;
ll power(ll a, ll b)
{
ll res = 1;
while (b > 0)
{
if (b % 2 == 1)
{
res = res * a % MOD;
}
b /= 2;
a = a * a % MOD;
}
return res;
}
const ll MAX = 5e5 + 10;
ll fact[MAX + 10], inv[MAX + 10], inv_fact[MAX + 10];
ll C(ll n, ll k)
{
if (n < k || k < 0) return 0;
return fact[n] * inv_fact[k] % MOD * inv_fact[n - k] % MOD;
}
ll extend1(queue<PIII> & q, map<PIII, ll> & da, map<PIII, ll> & db, vector<ll> & v)
{
int d = q.front().second;
ll res = 0;
while (q.size() && q.front().second == d)
{
auto t = q.front();
q.pop();
ll l = t.first.first, r = t.first.second;
int x = t.second;
ll w = v[x];
if (x < 0) continue;
if (w <= l)
{
PIII next = {{l - w, r}, x - 1};
if (db.count(next)) res = (res + (da[t] * db[next] % MOD) * C(l, w) % MOD) % MOD;
if (!da.count(next)) q.push(next);
da[next] = (da[next] + da[t] * C(l, w) % MOD) % MOD;
}
if (w <= r)
{
PIII next = {{l, r - w}, x - 1};
if (db.count(next)) res = (res + (da[t] * db[next] % MOD) * C(r, w) % MOD) % MOD;
if (!da.count(next)) q.push(next);
da[next] = (da[next] + da[t] * C(r, w) % MOD) % MOD;
}
}
return res % MOD;
}
ll extend2(ll lup, ll rup, queue<PIII> & q, map<PIII, ll> & da, map<PIII, ll> & db, vector<ll> & v)
{
int d = q.front().second;
ll res = 0;
while (q.size() && q.front().second == d)
{
auto t = q.front();
q.pop();
ll l = t.first.first, r = t.first.second;
int x = t.second + 1;
ll w = v[x];
if (x >= v.size()) continue;
if (l + w <= lup)
{
PIII next = {{l + w, r}, x};
if (da.count(next)) res = (res + (db[t] * da[next] % MOD) * C(l + w, w) % MOD) % MOD;
if (!db.count(next)) q.push(next);
db[next] = (db[next] + db[t] * C(l + w, w) % MOD) % MOD;
}
if (r + w <= rup)
{
PIII next = {{l, r + w}, x};
if (da.count(next)) res = (res + (db[t] * da[next] % MOD) * C(r + w, w) % MOD) % MOD;
if (!db.count(next)) q.push(next);
db[next] = (db[next] + db[t] * C(r + w, w) % MOD) % MOD;
}
}
return res % MOD;
}
ll bfs(ll l, ll r, vector<ll> & v)
{
if (l == 0 && r == 0) return 0;
queue<PIII> qa, qb;
map<PIII, ll> da, db;
qa.push({{l, r}, n - 1});
da[{{l, r}, n - 1}] = 1;
qb.push({{0, 0}, -1});
db[{{0, 0}, -1}] = 1;
while (qa.size() && qb.size())
{
ll t;
if (qa.size() <= qb.size())
t = extend1(qa, da, db, v);
else
t = extend2(l, r, qb, da, db, v);
if (t) return t;
}
return 0;
}
int main()
{
cin.tie(0);
ios::sync_with_stdio(false);
fact[0] = 1;
for (int i = 1; i <= MAX; i++)
fact[i] = fact[i - 1] * i % MOD;
inv_fact[MAX] = power(fact[MAX], MOD - 2);
for (int i = MAX - 1; i >= 0; i--)
inv_fact[i] = inv_fact[i + 1] * (i + 1) % MOD;
cin >> T;
while (T--)
{
vector<ll> v;
ll sum = 0, oz;
for (int i = 1; i <= 26; i++)
{
cin >> oz;
sum += oz;
if (oz) v.push_back(oz);
}
sort(v.begin(), v.end());
n = v.size();
ll res = bfs(sum / 2, sum / 2 + (sum & 1), v);
cout << res % MOD << endl;
}
return 0;
}
6.4 图的应用
6.4.1 最小生成树
Prim算法
c
int prim()
{
memset(d, 0x3f, sizeof d);
d[1] = 0;
int res = 0;
for (int i = 1; i <= n; i++)
{
int t = -1;
for (int j = 1; j <= n; j++)
if (!st[j] && (t == -1 || d[j] < d[t]))
t = j;
st[t] = true;
res += d[t];
for (int j = 1; j <= n; j++)
d[j] = min(d[j], w[t][j]);
}
return res;
}
Kruskal算法
c
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 110, M = 410;
struct Edge
{
int a, b, w;
bool used;
bool operator < (const Edge & t) const
{
return w < t.w;
}
} edges[M];
int n, k;
int p[N];
int find(int x)
{
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
ll kruskal()
{
sort(edges, edges + k);
for (int i = 1; i <= n; i++) p[i] = i;
ll res = 0;
for (int i = 0; i < k; i++)
{
int a = find(edges[i].a), b = find(edges[i].b), w = edges[i].w;
if (a != b)
{
p[a] = b;
res += w;
edges[i].used = true;
}
}
return res;
}
int main()
{
cin >> n >> k;
ll sum = 0;
for (int i = 0; i < k; i++)
{
int a, b, c;
cin >> a >> b >> c;
edges[i] = {a, b, c};
sum += c;
}
ll res = kruskal();
cout << res << endl;
return 0;
}
例六 严格次小生成树
c
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 100010, M = 300010, INF = 0x3f3f3f3f;
struct Edge
{
int a, b, w;
bool used;
bool operator < (const Edge & t) const
{
return w < t.w;
}
} edge[M];
int n, m;
int h[N], e[M], w[M], ne[M], idx;
int p[N];
int depth[N], fa[N][17], d1[N][17], d2[N][17];
int q[N];
void add(int a, int b, int c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}
int find(int x)
{
if (x != p[x]) p[x] = find(p[x]);
return p[x];
}
ll kruskal()
{
for (int i = 1; i <= n; i++) p[i] = i;
sort(edge, edge + m);
ll res = 0;
for (int i = 0; i < m; i++)
{
int a = find(edge[i].a), b = find(edge[i].b), w = edge[i].w;
if (a != b)
{
p[a] = b;
res += w;
edge[i].used = true;
}
}
return res;
}
void build()
{
memset(h, -1, sizeof h);
for (int i = 0; i < m; i++)
if (edge[i].used)
{
int a = edge[i].a, b = edge[i].b, w = edge[i].w;
add(a, b, w);
add(b, a, w);
}
}
void bfs()
{
memset(depth, 0x3f, sizeof depth);
depth[0] = 0, depth[1] = 1;
q[0] = 1;
int hh = 0, tt = 0;
while (hh <= tt)
{
int t = q[hh++];
for (int i = h[t]; ~i; i = ne[i])
{
int j = e[i];
if (depth[j] > depth[t] + 1)
{
depth[j] = depth[t] + 1;
q[++tt] = j;
fa[j][0] = t;
d1[j][0] = w[i], d2[j][0] = -INF;
for (int k = 1; k <= 16; k++)
{
int anc = fa[j][k - 1];
fa[j][k] = fa[anc][k - 1];
int dist[4] = {d1[j][k - 1], d2[j][k - 1], d1[anc][k - 1], d2[anc][k - 1]};
d1[j][k] = d2[j][k] = -INF;
for (int u = 0; u < 4; u++)
{
int d = dist[u];
if (d > d1[j][k]) d2[j][k] = d1[j][k], d1[j][k] = d;
else if (d != d1[j][k] && d > d2[j][k]) d2[j][k] = d;
}
}
}
}
}
}
int lca(int a, int b, int w)
{
static int dist[N * 2];
int cnt = 0;
if (depth[a] < depth[b]) swap(a, b);
for (int k = 16; k >= 0; k--)
if (depth[fa[a][k]] >= depth[b])
{
dist[cnt++] = d1[a][k];
dist[cnt++] = d2[a][k];
a = fa[a][k];
}
if (a != b)
{
for (int k = 16; k >= 0; k--)
if (fa[a][k] != fa[b][k])
{
dist[cnt++] = d1[a][k];
dist[cnt++] = d2[a][k];
dist[cnt++] = d1[b][k];
dist[cnt++] = d2[b][k];
a = fa[a][k], b = fa[b][k];
}
dist[cnt++] = d1[a][0];
dist[cnt++] = d1[b][0];
}
int dist1 = -INF, dist2 = -INF;
for (int i = 0; i < cnt; i++)
{
int d = dist[i];
if (d > dist1) dist2 = dist1, dist1 = d;
else if (d != dist1 && d > dist2) dist2 = d;
}
if (w > dist1) return w - dist1;
if (w > dist2) return w - dist2;
}
int main()
{
cin >> n >> m;
for (int i = 0; i < m; i++)
{
int a, b, c;
cin >> a >> b >> c;
edge[i] = {a, b, c};
}
ll sum = kruskal();
build();
bfs();
ll res = 1e18;
for (int i = 0; i < m; i++)
if (!edge[i].used)
{
int a = edge[i].a, b = edge[i].b, w = edge[i].w;
res = min(res, sum + lca(a, b, w));
}
cout << res << endl;
return 0;
}
6.4.2 最短路径
例七 垃圾箱分布
Dijkstra 只能用于不存在负权边的图。
c
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<ll, ll> PII;
const int N = 1050, M = 13, K = 10010, INF = 0x3f3f3f3f;
ll n, m, k, D;
ll h[N], e[K * 2], w[K * 2], ne[K * 2], idx;
map<string, ll> mp;
ll d[N];
bool st[N];
void add(ll a, ll b, ll c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}
void Dijkstra(ll S)
{
memset(d, 0x3f, sizeof d);
memset(st, 0, sizeof st);
priority_queue<PII, vector<PII>, greater<PII>> heap;
d[S] = 0;
heap.push({0, S});
while (heap.size())
{
auto [dist, ver] = heap.top();
heap.pop();
if (st[ver]) continue;
st[ver] = true;
for (int i = h[ver]; ~i; i = ne[i])
{
int j = e[i];
if (st[j]) continue;
if (d[j] > d[ver] + w[i])
{
d[j] = d[ver] + w[i];
heap.push({d[j], j});
}
}
}
}
string itoa(ll num)
{
string ret;
while (num) ret += (char)(num % 10 + '0'), num /= 10;
reverse(ret.begin(), ret.end());
return ret;
}
void solve()
{
cin >> n >> m >> k >> D;
for (int i = 1; i <= n; i++)
{
string r = itoa(i);
mp[r] = i;
}
ll idx = n;
mp["G1"] = ++idx, mp["G2"] = ++idx, mp["G3"] = ++idx;
mp["G4"] = ++idx, mp["G5"] = ++idx, mp["G6"] = ++idx;
mp["G7"] = ++idx, mp["G8"] = ++idx, mp["G9"] = ++idx;
mp["G10"] = ++idx;
memset(h, -1, sizeof h);
for (int i = 1; i <= k; i++)
{
string a, b;
ll dist;
cin >> a >> b >> dist;
ll u = mp[a], v = mp[b];
add(u, v, dist), add(v, u, dist);
}
ll MIN = -1, res = -1;
ll Aver;
for (int i = 1; i <= m; i++)
{
ll cur = i + n;
Dijkstra(cur);
ll mindist = LLONG_MAX, maxdist = 0;
ll Averdist = 0;
for (int j = 1; j <= n; j++)
{
Averdist += d[j];
mindist = min(mindist, d[j]);
maxdist = max(maxdist, d[j]);
}
if (maxdist > D) continue;
if (mindist > MIN || (mindist == MIN && Averdist < Aver))
{
res = i;
MIN = mindist;
Aver = Averdist;
}
}
if (res == -1) cout << "No Solution" << endl;
else
{
cout << fixed << setprecision(1);
cout << 'G' << res << endl;
double ans = (double)Aver / (double)n;
cout << (double)MIN << ' ' << round(ans * 10) / 10.0 << endl;
}
}
int main()
{
cin.tie(0);
ios::sync_with_stdio(false);
ll Case = 1;
while (Case --> 0) solve();
return 0;
}
例八 传送门
分层图
【题目大意】 :在一个无向图中,每条边属于一个颜色(协会),连续使用同一颜色的边不额外付费,切换颜色则需支付 1 1 1 金币,求从 1 1 1 到 n n n 的最少金币数。
c
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<ll, ll> PII;
#define x first
#define y second
const int N = 1000010, M = N * 2; //***
struct Edge
{
ll a, b, c;
} edge[M];
ll n, m;
ll h[N], e[M], w[M], ne[M], idx;
ll dist[N];
bool st[N];
void add(ll a, ll b, ll c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}
void Dijkstra(ll S)
{
priority_queue<PII, vector<PII>, greater<PII>> heap;
memset(dist, 0x3f, sizeof dist);
memset(st, 0, sizeof st);
dist[S] = 0;
heap.push({0, S});
while (heap.size())
{
auto t = heap.top();
heap.pop();
int ver = t.y;
if (st[ver]) continue;
st[ver] = true;
for (int i = h[ver]; ~i; i = ne[i])
{
int j = e[i];
if (dist[j] > dist[ver] + w[i])
{
dist[j] = dist[ver] + w[i];
if (!st[j]) heap.push({dist[j], j});
}
}
}
}
void solve()
{
cin >> n >> m;
ll cnt = 0, num = 0;
vector<set<ll>> graph(n + 10);
map<ll, ll> mp, mp2;
idx = 0;
memset(h, -1, sizeof h);
for (int i = 1; i <= m; i++)
{
ll a, b, c;
cin >> a >> b >> c;
if (!mp.count(c)) mp[c] = ++cnt;
if (!mp2.count(a + n * mp[c])) mp2[a + n * mp[c]] = ++num;
if (!mp2.count(b + n * mp[c])) mp2[b + n * mp[c]] = ++num;
add(mp2[a + n * mp[c]], mp2[b + n * mp[c]], 0);
add(mp2[b + n * mp[c]], mp2[a + n * mp[c]], 0);
graph[a].insert(mp2[a + n * mp[c]]);
graph[b].insert(mp2[b + n * mp[c]]);
}
ll S = 0, T = ++num;
for (int i = 1; i <= n; i++)
{
num++;
for (auto v : graph[i])
{
add(v, num, 0), add(num, v, 1);
if (i == 1) add(S, v, 1);
if (i == n) add(v, T, 0);
}
}
Dijkstra(S);
cout << dist[T] << '\n';
}
int main()
{
cin.tie(0);
ios::sync_with_stdio(false);
ll T;
cin >> T;
while (T--) solve();
return 0;
}
例九 【模板】差分约束
spfa
c
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 5010, M = N * 2;
ll n, m;
ll h[N], e[M], w[M], ne[M], idx;
ll dist[N];
ll q[N], cnt[N];
bool st[N];
void add(ll a, ll b, ll c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}
bool spfa()
{
memset(dist, 0x3f, sizeof dist);
dist[0] = 0;
st[0] = true;
q[0] = 0;
int hh = 0, tt = 1;
while (hh != tt)
{
int t = q[hh++];
if (hh == N) hh = 0;
st[t] = false;
for (int i = h[t]; ~i; i = ne[i])
{
int j = e[i];
if (dist[j] > dist[t] + w[i])
{
dist[j] = dist[t] + w[i];
cnt[j]++;
if (cnt[j] > n + 1) return false;
if (!st[j])
{
q[tt++] = j;
if (tt == N) tt = 0;
st[j] = true;
}
}
}
}
return true;
}
void solve()
{
cin >> n >> m;
memset(h, -1, sizeof h);
for (int i = 1; i <= m; i++)
{
ll a, b, c;
cin >> a >> b >> c;
add(b, a, c);
}
for (int i = 1; i <= n; i++) add(0, i, 0);
if (!spfa()) cout << "NO\n";
else
{
for (int i = 1; i <= n; i++)
cout << dist[i] << ' ';
cout << '\n';
}
}
int main()
{
cin.tie(0);
ios::sync_with_stdio(false);
ll Case = 1;
while (Case --> 0) solve();
return 0;
}
例十 影响力
L3-040 人生就像一场旅行 - 团体程序设计天梯赛-练习集
Floyd
c
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 510, M = 125000;
ll B, n, m, q;
ll d[N][N];
ll h[N][N];
int main()
{
cin >> B >> n >> m >> q;
memset(d, 0x3f, sizeof d);
for (int i = 0; i < m; i++)
{
ll a, b, c, w;
cin >> a >> b >> c >> w;
d[a][b] = d[b][a] = min(d[a][b], c);
h[a][b] = h[b][a] = max(h[a][b], w);
}
for (int i = 1; i <= n; i++)
d[i][i] = 0;
for (int k = 1; k <= n; k++)
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
if (d[i][j] > d[i][k] + d[k][j] || (d[i][j] == d[i][k] + d[k][j] && h[i][j] < h[i][k] + h[k][j]))
{
d[i][j] = d[i][k] + d[k][j];
h[i][j] = h[i][k] + h[k][j];
}
while (q--)
{
int v;
cin >> v;
vector<int> ach;
for (int i = 1; i <= n; i++)
if (i != v && d[v][i] <= B)
ach.push_back(i);
if (!ach.size())
{
cout << "T_T" << endl;
continue;
}
for (int i = 0; i < ach.size(); i++)
{
cout << ach[i];
if (i != ach.size() - 1) cout << " ";
}
cout << endl;
ll ma = 0;
for (int i = 0; i < ach.size(); i++)
ma = max(ma, h[v][ach[i]]);
vector<int> res;
for (int i = 0; i < ach.size(); i++)
if (h[v][ach[i]] == ma)
res.push_back(ach[i]);
for (int i = 0; i < res.size(); i++)
{
cout << res[i];
if (i != res.size() - 1) cout << " ";
}
cout << endl;
}
return 0;
}
6.4.3 有向无环图描述表达式
例十一 计算图
c
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 50010, INF = 0x3f3f3f3f;
struct Node
{
ll op;
ll lson, rson;
double lval, rval;
vector<ll> nxt;
double nout;
void count()
{
if (op == 1) nout = lval + rval;
else if (op == 2) nout = lval - rval;
else if (op == 3) nout = lval * rval;
else if (op == 4) nout = exp(lval);
else if (op == 5) nout = log(lval);
else if (op == 6) nout = sin(lval);
}
} node[N];
void pushdown(ll u)
{
node[u].count();
for (auto i : node[u].nxt)
{
if (node[i].op <= 3)
{
if (node[i].lson == u) node[i].lval = node[u].nout;
else node[i].rval = node[u].nout;
}
else node[i].lval = node[u].nout;
}
}
ll n;
ll T;
ll din[N], dout[N];
void Topsort()
{
queue<ll> Q;
for (int i = 1; i <= n; i++)
if (din[i] == 0)
Q.push(i);
while (Q.size())
{
ll cur = Q.front();
Q.pop();
pushdown(cur);
for (auto i : node[cur].nxt)
{
din[i]--;
if (din[i] == 0) Q.push(i);
}
}
}
double dfs(ll u, ll tar)
{
if (u == tar) return 1.0;
if (node[u].op == 0) return 0;
if (node[u].op == 1) return dfs(node[u].lson, tar) + dfs(node[u].rson, tar);
if (node[u].op == 2) return dfs(node[u].lson, tar) - dfs(node[u].rson, tar);
if (node[u].op == 3) return dfs(node[u].lson, tar) * node[u].rval + node[u].lval * dfs(node[u].rson, tar);
if (node[u].op == 4) return dfs(node[u].lson, tar) * node[u].nout;
if (node[u].op == 5) return dfs(node[u].lson, tar) / node[u].lval;
if (node[u].op == 6) return dfs(node[u].lson, tar) * cos(node[u].lval);
}
void solve()
{
cin >> n;
vector<ll> have;
for (int i = 1; i <= n; i++)
{
ll op;
cin >> op;
if (op == 0)
{
have.push_back(i);
double x;
cin >> x;
node[i] = {op, -1, -1, -1, -1, node[i].nxt, x};
}
else if (op <= 3)
{
ll x, y;
cin >> x >> y;
x++, y++;
node[i] = {op, x, y, -1, -1, node[i].nxt, -1};
node[x].nxt.push_back(i), node[y].nxt.push_back(i);
dout[x]++, dout[y]++, din[i] += 2;
}
else
{
ll x;
cin >> x;
x++;
node[i] = {op, x, x, -1, -1, node[i].nxt, -1};
node[x].nxt.push_back(i);
dout[x]++, din[i]++;
}
}
for (int i = 1; i <= n; i++)
if (dout[i] == 0)
{
T = i;
break;
}
Topsort();
cout << fixed << setprecision(3);
cout << node[T].nout << endl;
for (int i = 0; i < have.size(); i++)
{
cout << dfs(T, have[i]);
if (i != have.size() - 1) cout << ' ';
}
}
int main()
{
cin.tie(0);
ios::sync_with_stdio(false);
int Case = 1;
// cin >> Case;
while (Case --> 0) solve();
return 0;
}
6.4.4 拓扑排序
c
bool TopologicalSort(Graph G){
stack<int> S; // 初始化栈,存储入度为0的顶点
for (int i = 0; i < G.vexnum; i++)
if (indegree[i] == 0)
S.push(i); // 将所有入度为0的顶点入栈
int count = 0; // 计数,记录当前已经输出的顶点数
while (!S.empty()) { // 栈不空,则存在入度为0的顶点
int i = S.top();
S.pop(); // 栈顶元素出栈
print[count++] = i; // 输出顶点i
for (p = G.vertices[i].firstarc; p; p = p->nextarc) {
// 将所有i指向的顶点的入度减1,并且将入度减为0的顶点压入栈S
v = p->adjvex;
if (!(--indegree[v]))
S.push(v); // 入度为0,则入栈
}
}
if (count < G.vexnum)
return false; // 排序失败
else
return true; // 拓扑排序成功
}
例十二 千手观音
c
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> PII;
typedef pair<string, int> PSI;
#define x first
#define y second
const int N = 100010, M = 10010;
struct Node
{
vector<string> r;
} ns[N];
int n;
int h[M], e[N], ne[N], idx;
set<string> have;
unordered_map<string, int> id;
string id_r[M];
set<PII> edges;
int d[M];
void add(int a, int b)
{
if (edges.count({a, b})) return;
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
edges.insert({a, b});
d[b]++;
}
void work(int u)
{
for (int i = 0; i < ns[u].r.size(); i++)
{
int t1 = id[ns[u].r[i]], t2 = id[ns[u + 1].r[i]];
if (t1 != t2)
{
add(t1, t2);
break;
}
}
}
int main()
{
cin.tie(0);
ios::sync_with_stdio(false);
cin >> n;
memset(h, -1, sizeof h);
for (int i = 1; i <= n; i++)
{
vector<string> tmp;
string s;
cin >> s;
int cur = 0;
while (cur < s.size())
{
string t;
while (cur < s.size() && s[cur] != '.') t += s[cur], cur++;
have.insert(t);
tmp.push_back(t);
cur++;
}
ns[i].r = tmp;
}
int cnt = 0;
for (auto s : have) id[s] = ++cnt, id_r[cnt] = s;
for (int i = 1; i + 1 <= n; i++)
{
if (ns[i].r.size() != ns[i + 1].r.size()) continue;
work(i);
}
vector<string> res;
priority_queue<PSI, vector<PSI>, greater<PSI>> heap;
for (int i = 1; i <= cnt; i++)
if (d[i] == 0)
heap.push({id_r[i], i});
while (heap.size())
{
auto [x, u] = heap.top();
heap.pop();
res.push_back(x);
for (int i = h[u]; ~i; i = ne[i])
{
int j = e[i];
d[j]--;
if (d[j] == 0) heap.push({id_r[j], j});
}
}
for (int i = 0; i < res.size(); i++)
{
cout << res[i];
if (i != res.size() - 1) cout << '.';
}
cout << endl;
return 0;
}
例十三 Follow the Penguins
基环图
c
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<ll, ll> PII;
#define x first
#define y second
const ll N = 500010, INF = 1e10;
struct Node
{
double x0, xe;
} nodes[N];
ll n;
ll t[N], f[N], a[N], cnt, d[N], din[N];
bool inc[N], st[N];
vector<ll> block[N];
ll res[N];
void dfs1(ll u)
{
block[cnt].push_back(u);
st[u] = true;
if (!st[t[u]]) dfs1(t[u]);
}
void topsort()
{
deque<ll> q;
for (int i = 1; i <= n; i++) inc[i] = true;
for (int i = 1; i <= n; i++)
if (din[i] == 0)
q.push_back(i);
while (q.size())
{
int ver = q.front();
q.pop_front();
inc[ver] = false;
st[ver] = true;
din[t[ver]]--;
if (din[t[ver]] == 0) q.push_back(t[ver]);
}
for (int i = 1; i <= n; i++)
if (inc[i] && !st[i])
{
cnt++;
dfs1(i);
}
}
void count(ll u, ll v)
{
if (d[u] != d[v] && res[v] > fabs(a[u] - a[v]))
{
res[u] = fabs(a[u] - a[v]);
nodes[u].xe = min(a[u], a[v]) + fabs(a[u] - a[v]) / 2.0;
}
else
{
res[u] = fabs(nodes[v].xe - nodes[u].x0) * 2;
nodes[u].xe = nodes[t[u]].xe;
}
}
void dfs2(ll u)
{
if (res[u]) return;
count(u, t[u]);
dfs2(f[u]);
}
void work(ll id)
{
ll dist = INF, u, v;
for (int i = 0; i < block[id].size(); i++)
{
int p = block[id][i], q = block[id][(i + 1) % ((int) block[id].size())];
f[q] = p;
if (d[p] != d[q] && abs(a[p] - a[q]) < dist)
dist = abs(a[p] - a[q]), u = p, v = q;
}
res[u] = dist;
nodes[u].xe = nodes[u].x0 + 0.5 * d[u] * dist;
dfs2(f[u]);
}
void dfs3(ll u)
{
if (res[t[u]]) count(u, t[u]);
else
{
dfs3(t[u]);
count(u, t[u]);
}
}
void solve()
{
cin >> n;
for (int i = 1; i <= n; i++) cin >> t[i], din[t[i]]++;
for (int i = 1; i <= n; i++) cin >> a[i], nodes[i].x0 = a[i];
for (int i = 1; i <= n; i++)
if (a[i] < a[t[i]]) d[i] = 1;
else d[i] = -1;
topsort();
for (int i = 1; i <= cnt; i++) work(i);
for (int i = 1; i <= n; i++)
if (!inc[i] && !res[i])
dfs3(i);
for (int i = 1; i <= n; i++)
cout << res[i] << ' ';
cout << endl;
}
int main()
{
cin.tie(0);
ios::sync_with_stdio(false);
ll Case = 1;
while (Case --> 0) solve();
return 0;
}
6.4.5 关键路径
例十四 Commemorative Race
2019 年中中美洲地区 A 纪念 Race_ICPC --- A-Commemorative Race_ICPC Mid-Central USA Region 2019
c
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 100010, M = 2000010;
ll n, m;
ll h[N], e[M], ne[M], idx;
ll mx1[N], mx2[N];
ll cnt[N], res[N];
bool st[N];
void add(ll a, ll b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
ll dfs1(ll u)
{
if (st[u]) return mx1[u];
st[u] = true;
for (int i = h[u]; ~i; i = ne[i])
{
int j = e[i];
ll tmp = dfs1(j) + 1;
if (tmp > mx1[u]) mx2[u] = mx1[u], mx1[u] = tmp;
else if (tmp > mx2[u]) mx2[u] = tmp;
}
return mx1[u];
}
void dfs2(ll u, ll dep)
{
if (st[u]) return;
st[u] = true;
for (int i = h[u]; ~i; i = ne[i])
{
int j = e[i];
if (mx1[u] == mx1[j] + 1)
{
dfs2(j, dep + 1);
cnt[dep]++;
res[dep] = dep + mx2[u];
}
}
}
void solve()
{
cin >> n >> m;
memset(h, -1, sizeof h);
for (int i = 1; i <= n; i++) add(0, i);
for (int i = 1; i <= m; i++)
{
ll a, b;
cin >> a >> b;
add(a, b);
}
dfs1(0);
memset(st, 0, sizeof st);
dfs2(0, 0);
ll ans = mx1[0];
for (int i = 1; i <= n; i++)
if (cnt[i] == 1)
ans = min(ans, res[i]);
cout << ans - 1 << endl;
}
int main()
{
cin.tie(0);
ios::sync_with_stdio(false);
ll Case = 1;
while (Case --> 0) solve();
return 0;
}
参考文献
王道论坛. 数据结构考研复习指导. 电子工业出版社, 2027.