1 条题解

  • 0
    @ 2025-11-27 11:59:15

    题意概述

    • 在球面上有 NN 个点(塔),MM 条边(传输通道)。
    • 每条边 (u,v)(u,v) 的容量为: [ c = \frac{K \cdot q_u \cdot q_v}{r^2} ] 其中 rr 是球面距离。
    • 源点是 ss,汇点是 tt
    • 我们可以选择破坏 LL 个节点(不能是 sstt),删除这些节点以及与它们相连的所有边。
    • 目标是选择 LL 个要破坏的节点,使得破坏后 sstt最大流最小
    • 输出这个最小的最大流值。

    关键数据范围

    • N1000N \le 1000
    • MN(N1)2M \le \frac{N(N-1)}{2}
    • Lmin(8,N2)L \le \min(8, N-2)

    LL 很小,这提示我们可以枚举要破坏的节点集合。


    算法步骤

    1. 节点坐标计算

    输入给出的是 (θ,φ)(\theta, \varphi) 的“比例” (ai,bi)(a_i, b_i): [ \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. 球面距离计算

    对于两点 A=(x1,y1,z1)A=(x_1,y_1,z_1)B=(x2,y2,z2)B=(x_2,y_2,z_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) ] 因为两个点都在球面上,所以 p1=p2=R|\mathbf{p}_1| = |\mathbf{p}_2| = R

    注意题目保证不是对跖点,所以 r<πRr < \pi R,不会出现 arccos\arccos 参数为 1-1 的情况。

    3. 边容量计算

    对每条边 (u,v)(u,v): [ \text{capacity} = \frac{K \cdot q_u \cdot q_v}{r^2} ] 其中 rr 是上面算的球面距离。

    4. 最大流算法

    由于 N1000N \le 1000M5×105M \le 5\times 10^5,我们可以使用 Dinic 算法,复杂度 O(N2M)O(N^2 M) 在一般情况下较快,且实际图比较稀疏。

    5. 枚举破坏的节点

    • 可破坏的节点集合大小为 N2N-2(去掉 sstt)。
    • 要从中选 LL 个,组合数 CN2LC_{N-2}^L
    • N=1000,L=8N=1000, L=8 时,C9988C_{998}^{8} 太大(约 102010^{20}),不能直接枚举。

    优化

    • 实际上,破坏一个节点会删除与它相连的所有边。
    • 要使 ss-tt 流最小,应该破坏关键节点(在最小割中的节点)。
    • LL 很小,我们可以考虑只枚举与 sstt 较近的节点,或者使用随机化/贪心方法。

    但题目是计算题,不是构造题,所以我们需要精确求解。


    精确解法思路

    由于 L8L \le 8,我们可以枚举所有大小 L\le L 的节点子集吗?
    不行,CN2LC_{N-2}^L 太大。

    正确做法:
    破坏节点等价于在图中删除这些节点以及相连的边
    我们可以用状态压缩

    • dp[mask]dp[mask] 表示已经破坏了 maskmask 中的节点时,还能破坏的节点数以及当前的最小最大流。
    • 但状态数 2N22^{N-2} 太大。

    正确解法:迭代加深 / 搜索

    因为 LL 很小,我们可以用深度优先搜索,每次选择一个节点破坏,直到破坏 LL 个节点,然后计算最大流。

    但直接 DFS 选择 LL 个节点,复杂度 O(CN2LF)O(C_{N-2}^L \cdot F),其中 FF 是最大流计算时间,不可接受。


    重要优化:最小割观察

    最大流 = 最小割。
    破坏节点相当于把它的所有边容量置 0,这会影响最小割。

    一个有效剪枝:

    • 在搜索过程中,如果当前的最大流已经 \le 已知答案,则可以剪枝。
    • 初始时,先计算原图的最大流 f0f_0,作为答案上界。
    • 在搜索时,按某种顺序尝试破坏节点,并利用当前流值剪枝。

    更优方法:节点重要度排序

    我们可以按某种重要性对可破坏节点排序,只尝试最重要的 TT 个节点(TT 稍大于 LL),然后枚举这些节点的子集。

    重要性度量:

    • 节点度
    • ss-tt 路径上的次数
    • 删除该节点后流减少的量

    由于 LL 很小,我们可以取 T=min(20,N2)T = \min(20, N-2),然后枚举 CTLC_T^L,这通常可接受(C208125970C_{20}^8 \approx 125970)。


    算法框架

    1. 读入数据,计算每个节点的三维坐标。
    2. 计算每条边的长度和容量。
    3. 构建原图,计算初始最大流 f0f_0
    4. 对可破坏节点(非 s,ts,t)按重要性排序,取前 TT 个。
    5. 枚举这 TT 个节点中大小为 LL 的所有子集:
      • 从图中删除这些节点及相连边。
      • 计算新图的最大流。
      • 更新最小流答案。
    6. 输出最小流。

    实现细节

    • 使用 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;
    }
    

    复杂度分析

    • 坐标计算:O(N)O(N)
    • 边容量计算:O(M)O(M)
    • 最大流计算(Dinic):O(N2M)O(N^2 M),但实际远小于上界
    • 枚举子集:O(CTLF)O(C_T^L \cdot F),其中 FF 是最大流时间

    T=20,L=8T=20, L=8 时,C208=125970C_{20}^8 = 125970,在合理范围内。


    总结

    这道题结合了:

    1. 球面几何计算距离
    2. 图论建图
    3. 网络流求最大流
    4. 组合枚举找最优破坏方案

    关键优化在于利用 LL 小的特点,只枚举重要的候选节点,从而在可行时间内解决问题。

    • 1

    信息

    ID
    5656
    时间
    3000ms
    内存
    256MiB
    难度
    10
    标签
    递交数
    7
    已通过
    1
    上传者