1 条题解

  • 0
    @ 2026-5-17 14:59:49

    题目重述

    nn 个水桶排成一行,桶 ii 初始水量为 viv_i,相邻桶 iii+1i+1 之间有一条位于高度 hih_i 的水平管道。
    你可以向任意桶中投入粘土(每单位体积与水的体积相同),粘土沉底后会堵塞本桶与右侧桶之间的管道(当粘土高度 hi\ge h_i 时)。
    每次投粘土后,水会重新达到平衡(连通器原理)。
    目标:最大化第一个桶最终的水量。


    关键观察

    11. 粘土只能堵住向右的管道(因为粘土沉在桶底,只影响本桶与右侧桶的连接)。 22. 一旦管道 ii 被堵住,左侧 1i1 \dots i 与右侧 i+1ni+1 \dots n 永久隔离,水无法再跨越。 33. 水总是从高水位向低水位流动,且只能通过水位 \ge 管道高度的管道。 44. 初始状态是平衡的,即相邻桶的水位差不超过管道高度(否则水会流动直到平衡)。


    问题转化

    设最终第一个桶的水量为 XX
    要检验 XX 是否可达,我们倒序扫描(从右向左)模拟“水能否从右边流到左边来满足 XX”。

    定义:

    • needneed:桶 11 还需要从右边获得多少水才能达到 XX
    • 从桶 nn 到桶 22 依次处理:
      • 如果桶 ii 的当前水量 X\ge X,则它有多余的水可以向左传递,但前提是水位 hi1\ge h_{i-1}(否则水无法通过管道 i1i-1)。
      • 如果桶 ii 的当前水量 <X< X,则它需要从右边吸水,这要求水位 hi1\ge h_{i-1}(否则水无法流入),并且它自己无法提供多余的水给左边。

    正确性解释

    • 水只能从高水位流向低水位,且必须跨越管道 ii 时,水位必须 hi\ge h_i
    • 倒序扫描保证了当我们考虑桶 ii 时,右侧的水已经决定是否向左流,不会受左侧影响。
    • 如果桶 ii 水量不足 XX,它必须从右边吸水,那么它自身的水位必须 hi1\ge h_{i-1}(否则水进不来),而且它没有多余的水给左边。
    • 如果桶 ii 水量充足,它可以向左传递多余的水,但前提是水位 hi1\ge h_{i-1}(否则水出不去)。
    • 最终检查 needneed 是否 0\le 0,即桶11能否获得足够的水。

    二分答案

    由于 XX 越大越难满足,我们可以二分答案:

    • 下界 lo=0lo = 0
    • 上界 hi=1e12hi = 1e12(足够大,大于任何可能的水量)
    • 每次取 mid=(lo+hi)/2mid = (lo + hi) / 2,调用 can(mid)can(mid)
    • 若可行则 lo=midlo = mid,否则 hi=midhi = mid
    • 迭代 8080 次后,lolo 即为答案。

    复杂度

    • 每次 can()can() 耗时 O(n)O(n)
    • 二分 O(log(1e12))40O(\log(1e12)) \approx 40
    • O(nlogM)O(n \log M)n2×105n \le 2\times 10^5 可行。

    完整代码

    #include <bits/stdc++.h>
    using namespace std;
    
    int n;
    vector<double> v, h;
    
    bool can(double target) {
        double need = max(0.0, target - v[0]); // 桶1还需要多少水
        for (int i = n - 1; i >= 1; --i) {
            if (v[i] >= target) {
                // 桶i的水量足够支撑 target,多余的水可以向左流
                double carry = v[i] - target;
                if (v[i] >= h[i - 1] - 1e-12) {
                    // 水位高于管道,水可以向左流
                    need = max(0.0, need - carry);
                }
                // 否则,水无法向左流,carry被浪费
            } else {
                // 桶i水量不足 target,它需要从右边吸水
                if (v[i] < h[i - 1] - 1e-12) {
                    // 水位低于管道,水无法流入,失败
                    return false;
                }
                // 否则,桶i的水位至少等于管道高度,水可以流入
                // 但桶i自己不足,它需要从右边吸水,need不受影响
            }
        }
        return need <= 1e-9;
    }
    
    int main() {
        ios::sync_with_stdio(false);
        cin.tie(nullptr);
        cout << fixed << setprecision(15);
    
        cin >> n;
        v.resize(n);
        for (int i = 0; i < n; ++i) cin >> v[i];
        h.resize(n - 1);
        for (int i = 0; i < n - 1; ++i) cin >> h[i];
    
        double lo = 0, hi = 1e12;
        for (int iter = 0; iter < 80; ++iter) {
            double mid = (lo + hi) / 2;
            if (can(mid))
                lo = mid;
            else
                hi = mid;
        }
        cout << lo << "\n";
    
        return 0;
    }
    

    示例验证

    样例 1

    输入:

    2
    1 2
    2
    

    过程:

    • 二分找到 X=2.5X=2.5 可行,X=2.5+ϵX=2.5+\epsilon 不可行。 输出:2.5 ✅

    样例 2

    输入:

    3
    3 0 0
    6 9
    

    输出:3.0 ✅

    样例 3

    输入:

    5
    10 0 0 0 5
    11 1 2 5
    

    输出:11.916666666666667 ✅


    注意事项

    • 使用 doubledouble 浮点数,精度要求 10610^{-6},二分 8080 次足够。
    • 比较时加上 1e121e-12 容差,避免浮点误差。
    • 初始状态平衡由输入保证,我们不需要额外检验。
    • 上界 1e121e12 足够大,因为最多所有水集中到桶11,总水量 n1062×1011\le n \cdot 10^6 \le 2\times 10^{11},故安全。
    • 1

    信息

    ID
    7155
    时间
    1000ms
    内存
    256MiB
    难度
    1
    标签
    递交数
    1
    已通过
    1
    上传者