<think>题目要求使用C++,并且不使用vector和pair。我们可以使用数组和结构体来构建邻接表,并使用SPFA算法(题目中提到了SPFA)来解决。但是题目中要求使用SPFA算法,并且不用vector和pair,所以我们需要手动实现邻接表。
思路:
1. 构建两个图:原图和反向图(用于计算从各点到x的最短距离,即原图中从x到各点反向图的最短距离就是原图各点到x的最短距离)。
2. 使用SPFA算法分别计算:
- 从x到其他各点的最短距离(回程,在原图上计算)
- 从其他各点到x的最短距离(去程,在反向图上计算,相当于原图各点到x的距离)
3. 对于每头牛,去程+回程的距离,取最大值。
注意:题目中可能有重边,我们使用邻接表存储,重边都会存储下来,在SPFA中会自然处理。
邻接表实现:
- 使用结构体数组edge存储边,包括to(目标节点)、weight(边权)、next(下一条边的索引)
- 使用head数组,head[u]表示节点u的第一条边在edge数组中的位置
步骤:
- 初始化图:初始化head数组为-1(表示没有边),edge数组索引从0开始
- 加边函数:将新边加入edge数组,并更新head数组
由于边数最大为100000,所以需要开足够大的数组。
我们将原图称为G,反向图称为revG。
分别对G和revG进行SPFA,得到两个距离数组:
dist1[i]: 在原图G上,从x到i的最短距离(回程)
dist2[i]: 在反向图revG上,从x到i的最短距离(即原图中从i到x的最短距离,去程)
然后,对于每头牛i,计算dist1[i] + dist2[i]的最大值。
注意:SPFA需要队列,我们使用数组模拟循环队列。
代码结构:
1. 定义常量:最大节点数、最大边数、无穷大
2. 定义结构体Edge
3. 定义全局数组:head, rev_head, edge, rev_edge, dist1, dist2
4. 加边函数(原图和反向图同时加)
5. SPFA函数:传入图(head和edge数组),起点,距离数组
6. 主函数:读入数据,建图,运行两次SPFA(一次原图,一次反向图),计算结果
由于题目中节点数N<=1000,边数M<=100000,所以我们可以开数组。
具体实现:</think># 使用数组和结构体实现 SPFA 算法解决农场派对问题
以下是使用 C++ 实现 SPFA 算法解决农场派对问题的方案,完全避免使用 `vector` 和 `pair`,采用数组和结构体实现邻接表。
```cpp
#include <iostream>
#include <cstring>
#include <queue>
#include <climits>
using namespace std;
const int MAX_N = 1005; // 最大节点数
const int MAX_M = 100005; // 最大边数
const int INF = INT_MAX; // 无穷大值
// 边结构体
struct Edge {
int to; // 目标节点
int weight; // 边权重
int next; // 下一条边的索引
};
Edge graph_edges[MAX_M]; // 原图边数组
int graph_head[MAX_N]; // 原图头指针数组
int graph_edge_count = 0; // 原图边计数器
Edge rev_graph_edges[MAX_M]; // 反向图边数组
int rev_graph_head[MAX_N]; // 反向图头指针数组
int rev_graph_edge_count = 0;// 反向图边计数器
// 添加边到原图
void add_graph_edge(int from, int to, int weight) {
graph_edges[graph_edge_count] = {to, weight, graph_head[from]};
graph_head[from] = graph_edge_count++;
}
// 添加边到反向图
void add_rev_graph_edge(int from, int to, int weight) {
rev_graph_edges[rev_graph_edge_count] = {to, weight, rev_graph_head[from]};
rev_graph_head[from] = rev_graph_edge_count++;
}
// SPFA 算法实现
void spfa(Edge edges[], int head[], int dist[], int start, int n) {
// 初始化距离数组
for (int i = 1; i <= n; i++) {
dist[i] = INF;
}
dist[start] = 0;
// 使用队列存储待处理节点
queue<int> q;
q.push(start);
// 节点是否在队列中
bool in_queue[MAX_N] = {false};
in_queue[start] = true;
while (!q.empty()) {
int u = q.front();
q.pop();
in_queue[u] = false;
// 遍历所有邻接边
for (int i = head[u]; i != -1; i = edges[i].next) {
int v = edges[i].to;
int w = edges[i].weight;
// 松弛操作
if (dist[u] + w < dist[v]) {
dist[v] = dist[u] + w;
if (!in_queue[v]) {
q.push(v);
in_queue[v] = true;
}
}
}
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, m, x;
cin >> n >> m >> x;
// 初始化头指针数组
memset(graph_head, -1, sizeof(graph_head));
memset(rev_graph_head, -1, sizeof(rev_graph_head));
// 读取边并构建图和反向图
for (int i = 0; i < m; i++) {
int a, b, t;
cin >> a >> b >> t;
add_graph_edge(a, b, t); // 原图:a->b
add_rev_graph_edge(b, a, t); // 反向图:b->a(相当于原图的逆边)
}
// 距离数组
int dist_to[MAX_N]; // 从各点到x的距离
int dist_back[MAX_N]; // 从x到各点的距离
// 计算去程距离(从各点到x,使用反向图)
spfa(rev_graph_edges, rev_graph_head, dist_to, x, n);
// 计算回程距离(从x到各点,使用原图)
spfa(graph_edges, graph_head, dist_back, x, n);
// 求最长总路径
int max_path = 0;
for (int i = 1; i <= n; i++) {
// 跳过不可达节点
if (dist_to[i] == INF || dist_back[i] == INF) continue;
int total = dist_to[i] + dist_back[i];
if (total > max_path) {
max_path = total;
}
}
cout << max_path << endl;
return 0;
}
```
## 代码详细解释
### 1. 数据结构设计
```cpp
struct Edge {
int to; // 目标节点
int weight; // 边权重
int next; // 下一条边的索引
};
```
- `to`: 边指向的目标节点
- `weight`: 边的权重
- `next`: 存储下一条边的索引(链式存储结构)
### 2. 邻接表实现
```cpp
Edge graph_edges[MAX_M]; // 原图边数组
int graph_head[MAX_N]; // 原图头指针数组
int graph_edge_count = 0; // 原图边计数器
Edge rev_graph_edges[MAX_M]; // 反向图边数组
int rev_graph_head[MAX_N]; // 反向图头指针数组
int rev_graph_edge_count = 0;// 反向图边计数器
```
- `graph_head[u]` 存储节点 u 的第一条边在 `graph_edges` 中的索引
- 通过 `next` 指针形成链表,遍历节点 u 的所有邻接边
- 反向图用于计算从各点到派对农场 x 的距离
### 3. 添加边的函数
```cpp
void add_graph_edge(int from, int to, int weight) {
graph_edges[graph_edge_count] = {to, weight, graph_head[from]};
graph_head[from] = graph_edge_count++;
}
```
1. 创建新边:目标节点 `to`,权重 `weight`
2. 新边的 `next` 指向当前 `head[from]`
3. 更新 `head[from]` 指向新边的索引
4. 边计数器递增
### 4. SPFA 算法实现
```cpp
void spfa(Edge edges[], int head[], int dist[], int start, int n) {
// 初始化距离数组
for (int i = 1; i <= n; i++) {
dist[i] = INF;
}
dist[start] = 0;
queue<int> q;
q.push(start);
bool in_queue[MAX_N] = {false};
in_queue[start] = true;
while (!q.empty()) {
int u = q.front();
q.pop();
in_queue[u] = false;
// 遍历所有邻接边
for (int i = head[u]; i != -1; i = edges[i].next) {
int v = edges[i].to;
int w = edges[i].weight;
// 松弛操作
if (dist[u] + w < dist[v]) {
dist[v] = dist[u] + w;
if (!in_queue[v]) {
q.push(v);
in_queue[v] = true;
}
}
}
}
}
```
1. **初始化**:距离数组设为无穷大,起点距离为 0
2. **队列初始化**:起点加入队列
3. **循环处理**:
- 取出队列头部节点 u
- 遍历 u 的所有邻接边
- 对每条边执行松弛操作:如果通过 u 到达 v 的距离更短,则更新距离
- 如果 v 不在队列中,则加入队列
4. **结束条件**:队列为空
### 5. 主函数逻辑
```cpp
int main() {
// 初始化
memset(graph_head, -1, sizeof(graph_head));
memset(rev_graph_head, -1, sizeof(rev_graph_head));
// 构建图和反向图
for (int i = 0; i < m; i++) {
add_graph_edge(a, b, t); // 原图:a->b
add_rev_graph_edge(b, a, t); // 反向图:b->a
}
// 计算距离
spfa(rev_graph_edges, rev_graph_head, dist_to, x, n); // 去程距离
spfa(graph_edges, graph_head, dist_back, x, n); // 回程距离
// 计算最长路径
int max_path = 0;
for (int i = 1; i <= n; i++) {
if (dist_to[i] != INF && dist_back[i] != INF) {
max_path = max(max_path, dist_to[i] + dist_back[i]);
}
}
}
```
1. **初始化**:头指针数组设为 -1(表示空链表)
2. **构建图**:
- 原图存储正常方向边
- 反向图存储反向边(用于计算从各点到 x 的距离)
3. **计算距离**:
- 在反向图上计算从 x 到各点的距离(即原图中各点到 x 的距离)
- 在原图上计算从 x 到各点的距离(回程距离)
4. **计算结果**:遍历所有节点,计算最大往返距离
## 算法分析
1. **时间复杂度**:
- SPFA 平均时间复杂度 O(kM),其中 k 是常数
- 最坏情况 O(NM),但实际应用中通常表现良好
- 对于稀疏图,SPFA 效率优于 Dijkstra
2. **空间复杂度**:
- 邻接表存储:O(N + M)
- 距离数组:O(N)
- 队列:O(N)
3. **反向图技巧**:
- 在反向图上从 x 运行 SPFA,得到的是原图中各点到 x 的最短距离
- 避免了对每个节点运行 SPFA 的 O(NM) 复杂度
4. **重边处理**:
- 邻接表自然支持重边,SPFA 会自动选择最短路径