第六章 图

图 { 图的定义 图结构的存储 { 邻接矩阵法、邻接表法 邻接多重表、十字链表 图的遍历 { 深度优先遍历 广度优先遍历 图的相关应用 { 最小生成树: 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

Problem - F - Codeforces

给定 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 的一个顶点),满足:

  1. 对每个 i i i, S i S_i Si 在 H H H 中要么是独立集 (内部无边),要么是完全子图(内部全连边);
  2. 对 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是以邻接表存储的图类型
例二 精神胜利

Problem - B - Codeforces

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入队
            }
    }
}
例三 力的平衡

1009 力的平衡

【题目描述】

在物理实验室中,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

Problem - D - Codeforces

双向广搜

【题目大意】 您希望构造一个由小写拉丁字母组成的字符串 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;
}
例六 严格次小生成树

P4180 [BJWC2010] 严格次小生成树 - 洛谷

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 最短路径

例七 垃圾箱分布

L3-005 垃圾箱分布 - 团体程序设计天梯赛-练习集

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;
}
例八 传送门

1005 传送门

分层图

【题目大意】 :在一个无向图中,每条边属于一个颜色(协会),连续使用同一颜色的边不额外付费,切换颜色则需支付 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;
}
例九 【模板】差分约束

P5960 【模板】差分约束 - 洛谷

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 有向无环图描述表达式

例十一 计算图

L3-023 计算图 - 团体程序设计天梯赛-练习集

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;                // 拓扑排序成功
}
例十二 千手观音

L3-031 千手观音 - 团体程序设计天梯赛-练习集

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

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.

学习资源 | 计算机考研杂货铺

编程题 - 题目列表 - 团体程序设计天梯赛-练习集

STD Contest List

Codeforces

luogu.com.cn

牛客竞赛OJ_ACM/NOI/CSP/CCPC/ICPC_信息学编程算法训练平台

[补题]ICPC Mid-Central USA Region 2019_最长路 icpc-CSDN博客

相关推荐
workflower20 小时前
用硬件换时间”与“用算法降成本”之间的博弈
人工智能·算法·安全·集成测试·无人机·ai编程
重生之我是Java开发战士21 小时前
【动态规划】简单多状态dp问题:按摩师,打家劫舍,删除并获得点数,粉刷房子,买卖股票的最佳时机
算法·动态规划·哈希算法
KAU的云实验台1 天前
单/多UAV、静/动态路径规划,基于PlatEMO平台的带约束多目标优化 本文核心内容:
算法·matlab·无人机
Liangwei Lin1 天前
洛谷 P1807 最长路
数据结构·算法
会编程的土豆1 天前
【数据结构与算法】二叉树从建立开始
数据结构·c++·算法
_日拱一卒1 天前
LeetCode:最大子数组和
数据结构·算法·leetcode
计算机安禾1 天前
【数据结构与算法】第22篇:线索二叉树(Threaded Binary Tree)
c语言·开发语言·数据结构·学习·算法·链表·visual studio code
算法鑫探1 天前
解密2025数字密码:数位统计之谜
c语言·数据结构·算法·新人首发
计算机安禾1 天前
【数据结构与算法】第21篇:二叉树遍历的经典问题:由遍历序列重构二叉树
c语言·数据结构·学习·算法·重构·visual studio code·visual studio