1 条题解
-
0
题意概述
- 在球面上有 个点(塔), 条边(传输通道)。
- 每条边 的容量为: [ c = \frac{K \cdot q_u \cdot q_v}{r^2} ] 其中 是球面距离。
- 源点是 ,汇点是 。
- 我们可以选择破坏 个节点(不能是 或 ),删除这些节点以及与它们相连的所有边。
- 目标是选择 个要破坏的节点,使得破坏后 到 的最大流最小。
- 输出这个最小的最大流值。
关键数据范围
很小,这提示我们可以枚举要破坏的节点集合。
算法步骤
1. 节点坐标计算
输入给出的是 的“比例” : [ \theta_i = \pi a_i,\quad \varphi_i = \pi b_i ] 直角坐标: [ x_i = R \sin\theta_i \cos\varphi_i,\quad y_i = R \sin\theta_i \sin\varphi_i,\quad z_i = R \cos\theta_i ]
2. 球面距离计算
对于两点 和 ,它们在球面上的欧氏距离: [ d_{\text{Euclidean}} = \sqrt{(x_1-x_2)^2 + (y_1-y_2)^2 + (z_1-z_2)^2} ] 球面距离(劣弧长): [ r = R \cdot \arccos\left(\frac{\mathbf{p}_1 \cdot \mathbf{p}_2}{R^2}\right) = R \cdot \arccos\left(\frac{x_1x_2 + y_1y_2 + z_1z_2}{R^2}\right) ] 因为两个点都在球面上,所以 。
注意题目保证不是对跖点,所以 ,不会出现 参数为 的情况。
3. 边容量计算
对每条边 : [ \text{capacity} = \frac{K \cdot q_u \cdot q_v}{r^2} ] 其中 是上面算的球面距离。
4. 最大流算法
由于 ,,我们可以使用 Dinic 算法,复杂度 在一般情况下较快,且实际图比较稀疏。
5. 枚举破坏的节点
- 可破坏的节点集合大小为 (去掉 和 )。
- 要从中选 个,组合数 。
- 当 时, 太大(约 ),不能直接枚举。
优化:
- 实际上,破坏一个节点会删除与它相连的所有边。
- 要使 - 流最小,应该破坏关键节点(在最小割中的节点)。
- 但 很小,我们可以考虑只枚举与 或 较近的节点,或者使用随机化/贪心方法。
但题目是计算题,不是构造题,所以我们需要精确求解。
精确解法思路
由于 ,我们可以枚举所有大小 的节点子集吗?
不行, 太大。正确做法:
破坏节点等价于在图中删除这些节点以及相连的边。
我们可以用状态压缩:- 设 表示已经破坏了 中的节点时,还能破坏的节点数以及当前的最小最大流。
- 但状态数 太大。
正确解法:迭代加深 / 搜索
因为 很小,我们可以用深度优先搜索,每次选择一个节点破坏,直到破坏 个节点,然后计算最大流。
但直接 DFS 选择 个节点,复杂度 ,其中 是最大流计算时间,不可接受。
重要优化:最小割观察
最大流 = 最小割。
破坏节点相当于把它的所有边容量置 0,这会影响最小割。一个有效剪枝:
- 在搜索过程中,如果当前的最大流已经 已知答案,则可以剪枝。
- 初始时,先计算原图的最大流 ,作为答案上界。
- 在搜索时,按某种顺序尝试破坏节点,并利用当前流值剪枝。
更优方法:节点重要度排序
我们可以按某种重要性对可破坏节点排序,只尝试最重要的 个节点( 稍大于 ),然后枚举这些节点的子集。
重要性度量:
- 节点度
- 在 - 路径上的次数
- 删除该节点后流减少的量
由于 很小,我们可以取 ,然后枚举 ,这通常可接受()。
算法框架
- 读入数据,计算每个节点的三维坐标。
- 计算每条边的长度和容量。
- 构建原图,计算初始最大流 。
- 对可破坏节点(非 )按重要性排序,取前 个。
- 枚举这 个节点中大小为 的所有子集:
- 从图中删除这些节点及相连边。
- 计算新图的最大流。
- 更新最小流答案。
- 输出最小流。
实现细节
- 使用 Dinic 算法求最大流。
- 三维点积计算距离时注意精度。
- 删除节点时,可以在最大流算法中把与这些节点相连的边容量置 0,或者建新图。
参考代码(C++ 框架)
#include <bits/stdc++.h> using namespace std; const int MAXN = 1005; const double PI = acos(-1.0); const double INF = 1e18; const double EPS = 1e-9; int N, M, L, s, t; double R, K; struct Point { double x, y, z; Point() {} Point(double x, double y, double z) : x(x), y(y), z(z) {} }; Point towers[MAXN]; double q[MAXN]; // 计算球面距离 double sphericalDist(const Point& a, const Point& b) { double dot = a.x*b.x + a.y*b.y + a.z*b.z; double cosVal = dot / (R*R); // 避免浮点误差 if (cosVal > 1.0) cosVal = 1.0; if (cosVal < -1.0) cosVal = -1.0; return R * acos(cosVal); } // Dinic 最大流算法 struct Dinic { struct Edge { int to, rev; double cap; }; vector<Edge> adj[MAXN]; int level[MAXN], iter[MAXN]; void addEdge(int from, int to, double cap) { adj[from].push_back({to, (int)adj[to].size(), cap}); adj[to].push_back({from, (int)adj[from].size()-1, 0.0}); } void bfs(int s) { memset(level, -1, sizeof(level)); queue<int> q; level[s] = 0; q.push(s); while (!q.empty()) { int v = q.front(); q.pop(); for (auto& e : adj[v]) { if (e.cap > EPS && level[e.to] < 0) { level[e.to] = level[v] + 1; q.push(e.to); } } } } double dfs(int v, int t, double f) { if (v == t) return f; for (int& i = iter[v]; i < adj[v].size(); i++) { Edge& e = adj[v][i]; if (e.cap > EPS && level[v] < level[e.to]) { double d = dfs(e.to, t, min(f, e.cap)); if (d > EPS) { e.cap -= d; adj[e.to][e.rev].cap += d; return d; } } } return 0.0; } double maxFlow(int s, int t) { double flow = 0.0; while (true) { bfs(s); if (level[t] < 0) return flow; memset(iter, 0, sizeof(iter)); double f; while ((f = dfs(s, t, INF)) > EPS) { flow += f; } } } void clear() { for (int i = 1; i <= N; i++) { adj[i].clear(); } } }; vector<pair<int, int>> edges; double capacity[MAXN][MAXN]; vector<int> candidates; int main() { ios::sync_with_stdio(false); cin.tie(0); cin >> N >> M >> L >> s >> t; cin >> R >> K; for (int i = 1; i <= N; i++) { double a, b, q_val; cin >> a >> b >> q_val; double theta = PI * a; double phi = PI * b; double x = R * sin(theta) * cos(phi); double y = R * sin(theta) * sin(phi); double z = R * cos(theta); towers[i] = Point(x, y, z); q[i] = q_val; } // 计算所有边的容量 for (int i = 0; i < M; i++) { int u, v; cin >> u >> v; edges.push_back({u, v}); double dist = sphericalDist(towers[u], towers[v]); double cap = K * q[u] * q[v] / (dist * dist); capacity[u][v] = capacity[v][u] = cap; } // 构建候选节点(非 s, t) for (int i = 1; i <= N; i++) { if (i != s && i != t) { candidates.push_back(i); } } // 按节点度排序(简单重要性度量) sort(candidates.begin(), candidates.end(), [&](int a, int b) { int degA = 0, degB = 0; for (auto& e : edges) { if (e.first == a || e.second == a) degA++; if (e.first == b || e.second == b) degB++; } return degA > degB; }); // 只取前 T 个候选 int T = min(20, (int)candidates.size()); if (candidates.size() > T) { candidates.resize(T); } // 初始最大流 Dinic dinic; for (auto& e : edges) { int u = e.first, v = e.second; dinic.addEdge(u, v, capacity[u][v]); } double initialFlow = dinic.maxFlow(s, t); double ans = initialFlow; // 枚举所有大小为 L 的子集 vector<int> selected(L); function<void(int, int)> dfs = [&](int pos, int start) { if (pos == L) { // 计算删除 selected 中节点后的最大流 Dinic d; vector<bool> deleted(N+1, false); for (int x : selected) deleted[x] = true; for (auto& e : edges) { int u = e.first, v = e.second; if (deleted[u] || deleted[v]) continue; d.addEdge(u, v, capacity[u][v]); } double flow = d.maxFlow(s, t); ans = min(ans, flow); return; } for (int i = start; i < candidates.size(); i++) { selected[pos] = candidates[i]; dfs(pos + 1, i + 1); } }; dfs(0, 0); cout << fixed << setprecision(15) << ans << endl; return 0; }
复杂度分析
- 坐标计算:
- 边容量计算:
- 最大流计算(Dinic):,但实际远小于上界
- 枚举子集:,其中 是最大流时间
当 时,,在合理范围内。
总结
这道题结合了:
- 球面几何计算距离
- 图论建图
- 网络流求最大流
- 组合枚举找最优破坏方案
关键优化在于利用 小的特点,只枚举重要的候选节点,从而在可行时间内解决问题。
- 1
信息
- ID
- 5656
- 时间
- 3000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 7
- 已通过
- 1
- 上传者