洪水题解

【洪水】题解

题目大意

\(n\)个水库,每个水库有一个容量上限\(a_i\),第\(i\)个水库到第\(i+1\)个水库有一个流量上限\(b_i\),最后一个水库流入大海。且每秒会有\(F\)流入第\(1\)个水库,问水库多久会溢出,不会溢出输出\(-1\)

对于n小于4的数据,直接模拟即可

对于所有水库容量相同,河道一样宽的数据

因为所有水库都相同,所以除第一个水库外,其余水库都是水流进来多少就流出去多少,不需要蓄水

所以当前答案就是\(\frac{a_1}{F-b_1}\)

如果\(F\le b_1\),输出\(-1\)

满分做法

考虑贪心

我们肯定是要让尽可能多的水往后流,这点是非常清楚的

而且水库溢出的条件其实就只有第一个水库溢出(为什么下面讲)

如果第\(1\)个水库没有满,那么我们就可以一直装到满为止。

如果中间有个水库已经满了,我们就可以减少流向这个水库的水,将多余的水存在前面的水库,直至第一个水库

这么一来,我们就得到了一种比较优秀的做法

  • 我们先算出每个水库群的净流入(因为越到后面流量肯定是单调不升的,其实就是个前缀最小值)

  • 然后我们找到第一个满的水库群\(i\),将其净流入全部存入\(i-1\)个水库群(就是让第\(i-1\)个水库群的净流量\(+\)\(i\)个水库群的净流量,一直这样到\(1\)号水库群为止)

这里的净流入和水库群的定义是这样的

水库群:有多个连续的水库组成,这几个水库里的水基本可以任意分配

要怎么样才能满足水库群?

设流入这个水库群的流量为\(F\),往右找到第一个流量\(<F\)的水库,这些水库就是一个水库群

我们计入水库群的容量为水库群中所有水库容量之和,净流入为这个水库群的总流入-总流出

净流入:水库群中自然状况下每秒必须流入的水量

画张图

无标题.png

显然,当这些水库中没有任何一个水库群时,那么水便可以一直从头流到尾,输出\(-1\)

显然,当一个水库群满时,我们需要将此水库群的净流入修改为\(0\)

那么每秒消失的水去哪里了呢?我们将前一个水库群的净流入\(+=\)这个水库的净流入,就是要让前一个水库群帮这个水库群分担压力

直到第一个水库群,没有所谓的前一个水库群,此时水库终于溢出

如此,此题完结

#include <bits/stdc++.h>
#define int long long
using namespace std ;
const int MAXN = 3e5 + 5 ;
int n , F ;
int a[ MAXN ] , b[ MAXN ] ;
int sum[ MAXN ] , t[ MAXN ] , tot = 1 ; // 水库群的容量,净流入和水库群的数量
inline int read () {
    int tot = 0 , f = 1 ; char c = getchar () ;
    while ( c < '0' || c > '9' ) { if ( c == '-' ) f = -1 ; c = getchar () ; }
    while ( c >= '0' && c <= '9' ) { tot = tot * 10 + c - '0' ; c = getchar () ; }
    return tot * f ;
}
signed main () {
    // freopen ( "ex_flood2.in" , "r" , stdin ) ;
    // freopen ( "B.out" , "w" , stdout ) ;
    n = read () ; F = read () ;
    for ( int i = 1 ; i <= n ; i ++ )
        a[ i ] = read () ;
    for ( int i = 1 ; i <= n ; i ++ )
        b[ i ] = read () ;
    for ( int i = 1 ; i <= n ; i ++ ) {
        if ( b[ i ] < F ) { //前缀最小值,当前的流量
            // cout << F - b[ i ] << endl ;
            t[ tot ] = F - b[ i ] ; sum[ tot ++ ] += a[ i ] ; F = b[ i ] ; //计算净流入

        }
        else sum[ tot ] += a[ i ] ;//水库群容量上升
    } tot -- ;
    if ( tot == 0 ) { //没有任何一个水库群
        printf ( "-1\n" ) ;
        return 0 ;
    }
    int pos ;
    // cout << pos << endl ;
    double ans = 0 ;
    while ( 1 ) {
        double minx = 9999999999999 ;
        for ( int i = 1 ; i <= tot ; i ++ ) {
            if ( t[ i ] == 0 ) continue ;//防止除0而RE
            if ( minx > (double) sum[ i ] / (double) t[ i ] ) { //找到第一个溢出的水库群
                minx = (double) sum[ i ] / (double) t[ i ] ;
                pos = i ;//记录下标
            }
        }
        for ( int i = 1 ; i <= n ; i ++ ) sum[ i ] -= t[ i ] * minx ; //因为这个水库群装满需要minx秒,所以其他水库群的容量也要减少
        ans += minx ;//记录答案
        if ( pos == 1 ) break ; //做到第一个水库就不能往前继续推了
        // cout << ans << " " << pos << endl ;
        t[ pos - 1 ] += t[ pos ] ;//分担压力
        t[ pos ] = 0 ;//净流量降为0
    }
    printf ( "%.3lf\n" , ans ) ;
    return 0 ;
}

感觉这题还能用二分答案+乱搞秒掉,有兴趣的可以试试看

posted @ 2020-08-12 10:26  hulean  阅读(206)  评论(0编辑  收藏  举报