CF【1700D】D. River Locks(dp、二分、数学)

4 篇文章 0 订阅
该博客介绍了如何利用贪心和动态规划策略解决一个关于水库填满的数学问题。首先,通过贪心算法确定水口应尽量开启在前面,以减少溢出的时间。然后,通过二分查找确定在限定时间内填满所有水箱所需的最少水口数量,并计算最早完成时间。博主还解析了为何维护dp数组能帮助求解此问题,并提供了C++代码实现。
摘要由CSDN通过智能技术生成

TP

借鉴博客

完善了下细节.

题意:

  • 一个大水库由 n 个体积为 v i vi vi 升的水箱构成,每个水箱都有一个水口,开启后每秒加一升水。如果当前水箱 i 满了,多余的水会超自然地留到 i + 1 的水箱中,直到最后留到大海。
  • q 次询问,每次给一个时间 t j tj tj ,问在给定时间内填满 n 个水箱最少要开启的水口数为多少,如果不能填满输出 -1.

思路:

  • 显然第一步贪心,如果要开 x 个水口就开在 [1,x] ,在前面开,水溢出后可以补给后面水箱,填满的时间更短。
  • 第二步比较容易走偏。其实二分都会想到,但不知道怎么下手。这里应该先推导一个公式:
  • 假设已知水库总量 S,给定限定时间为 T;在这个时间限制内,要开的最少水口数其实已经确定了!,开启水口数 c n t > = S / T cnt >= S/T cnt>=S/T (上取整),满足最小所以取等于
  • cnt 确定了,之前贪心也知道是开在前 cnt 个,剩下需要确定的就是 填好前 i 个水箱的最小时间
  • 所以第三步,设置 dp 状态,转移,加上二分优化,这题就解决了。1900的含金量

剩下看注释

C o d e : Code: Code:

#include<bits/stdc++.h>
#include<unordered_map>
#include<unordered_set>
#define mem(a,b) memset(a,b,sizeof a)
#define cinios (ios::sync_with_stdio(false),cin.tie(0),cout.tie(0))
#define forr(a,b,c) for(int a=b;a<=c;a++)
#define all(a) a.begin(),a.end()
#define sz(a) (int)a.size()
#define oper(a) operator<(const a& ee)const
#define endl '\n'
#define ul (u << 1)
#define ur (u << 1 | 1)
using namespace std;

typedef long long ll;
typedef pair<int, int> PII;

const int N = 2e5 + 10, M = 2e5 + 10, mod = 1e8;
int INF = 0x3f3f3f3f; ll LNF = 0x3f3f3f3f3f3f3f3f;
int n, m, k;
//很明显的贪心,水口尽可能在前开启
int v[N], dp[N];//开启前 i 个水口,同时填满前 i 个水箱的最早时间
ll sum[N];//水量前缀和

void work() {
    dp[1] = v[1], sum[1] = v[1];
    for (int i = 2; i <= n; i++) {
        sum[i] = sum[i - 1] + v[i];

        //dp[i] 最早的时间也至少是 dp[i-1],前面填好了才能覆盖后面
        int l = dp[i - 1], r = 1e9;
        while (l < r)
        {
            int mid = l + r >> 1;
            //mid是时间,i 是水口数
            if (1ll * mid * i < sum[i])l = mid + 1;
            else r = mid;
        }
        dp[i] = l;
    }
}

void solve() {
    cin >> n;
    forr(i, 1, n)cin >> v[i];
    work();

    //明明我们要求的是限定时间内灌满整个水库的最少水口数,为什么dp维护的是这个呢?

    //因为已知水库总量 S,给定限定时间 T;
    //开启水口数 cnt >= S/T (上取整),满足最小所以取等于;
    // 
    // 意为:满足这个开启数后,水库一定能填满,但时间不知,
    // 因为单个 vi 的限制。
    // 
    //知道要开启的水口数后,我们可以直接代入到 dp 中找到最早时间,比较即可

    cin >> m;
    while (m--)
    {
        int tim; cin >> tim;
        ll g = (sum[n] + tim - 1) / tim;

        if (g <= n && dp[g] <= tim)cout << g << endl;
        else cout << -1 << endl;
    }
}

int main() {
    cinios;
    int T = 1;
    for (int t = 1; t <= T; t++)
        solve();
    return 0;
}
/*
*/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值