2020年NOIP提高组排水系统(洛谷P7113):从拓扑排序到分数运算

一、问题背景
2020年NOIP提高组的排水系统题目考察了图论中的拓扑排序和分数运算能力。题目模拟了一个城市排水系统,要求计算最终出水口的水量分配比例。
二、算法核心思路
三、代码解析(完整注释版)
#include <iostream>
#include <vector>
#include <queue>
#include <numeric> // 用于gcd函数
using namespace std;
// 分数结构体:处理精确分数运算
struct Fraction {
long long p, q; // 分子p,分母q
// 构造函数:初始化分数并自动约分
Fraction(long long _p = 0, long long _q = 1) : p(_p), q(_q) {
normalize();
}
// 分数约分方法
void normalize() {
if (q < 0) { p = -p; q = -q; } // 保证分母为正
long long g = gcd(abs(p), abs(q)); // 计算最大公约数
p /= g;
q /= g;
}
// 重载+运算符:实现分数加法
Fraction operator+(const Fraction& other) const {
long long lcm = q / gcd(q, other.q) * other.q; // 计算最小公倍数
return Fraction(p * (lcm / q) + other.p * (lcm / other.q), lcm);
}
// 重载/运算符:实现分数除以整数
Fraction operator/(int d) const {
return Fraction(p, q * d); // 分母乘以除数
}
// 静态方法:计算最大公约数(欧几里得算法)
static long long gcd(long long a, long long b) {
return b ? gcd(b, a % b) : a;
}
};
int main() {
ios::sync_with_stdio(false); // 加速C++输入输出
cin.tie(nullptr);
int n, m; // n-排水节点总数,m-入水口数量
cin >> n >> m;
// 图的邻接表表示
vector<vector<int>> adj(n+1); // 节点编号1~n
vector<int> out_degree(n+1, 0); // 每个节点的出度
vector<int> in_degree(n+1, 0); // 每个节点的入度
// 构建图结构
for (int i = 1; i <= n; ++i) {
cin >> out_degree[i]; // 读取当前节点的出度
adj[i].resize(out_degree[i]); // 调整邻接表大小
for (int j = 0; j < out_degree[i]; ++j) {
cin >> adj[i][j]; // 读取相邻节点
in_degree[adj[i][j]]++; // 更新相邻节点的入度
}
}
vector<Fraction> flow(n+1); // 每个节点的水流分数
queue<int> q; // 拓扑排序队列
// 初始化:所有入水口流量设为1/1
for (int i = 1; i <= m; ++i) {
flow[i] = Fraction(1, 1);
q.push(i); // 入水口入队
}
// 拓扑排序处理流程
while (!q.empty()) {
int u = q.front();
q.pop();
if (out_degree[u] == 0) continue; // 出水口不处理
Fraction split = flow[u] / out_degree[u]; // 分流计算
// 处理所有相邻节点
for (int v : adj[u]) {
flow[v] = flow[v] + split; // 累加流量
if (--in_degree[v] == 0) { // 入度减1,若为0则入队
q.push(v);
}
}
}
// 输出所有出水口的结果
for (int i = 1; i <= n; ++i) {
if (out_degree[i] == 0) {
cout << flow[i].p << " " << flow[i].q << "\n";
}
}
return 0;
}四、关键知识点详解
1. 分数运算实现
代码中自定义的Fraction结构体实现了:
自动约分功能(normalize方法)
分数加法(operator+重载)
分数除以整数(operator/重载)
最大公约数计算(gcd静态方法)
2. 拓扑排序应用
通过维护入度数组(in_degree)和队列(q)实现:
初始时将入度为0的节点(入水口)入队
处理队列中的节点,将其流量分配给后继节点
后继节点入度减为0时入队
3. 性能优化
使用
ios::sync_with_stdio(false)加速输入输出使用邻接表存储图结构,空间复杂度O(n+m)
五、常见问题解答
Q: 为什么需要分数运算而不是浮点数? A: 浮点数存在精度问题,在多次运算后可能产生误差,而分数运算能保证精确性。
Q: 如何处理大数运算? A: 使用long long类型存储分子分母,注意约分防止溢出。
Q: 为什么使用拓扑排序? A: 排水系统是有向无环图,拓扑排序能确保按正确顺序处理节点。
原创内容 转载请注明出处
