Codeforces-1700 D: River Locks 【动态规划、二分查找】

Codeforces-1700 D: River Locks

题目传送门:Codeforces-1700

题目

题目截图

在这里插入图片描述

样例描述

在这里插入图片描述

题目大意

  直线上有一个长度为 n n n 的水坝,水坝上每个单位长度上有一个容器,第 i i i 个容器可以存储 v i v_i vi 的容器,每个容器上面有一个水管,一个水管打开后会向下面的容器注入 1 1 1 单位的水,当 i i i 容器被注满水后,新增的水会瞬间转移到 i + 1 i+1 i+1 容器中。若最后一个容器被注满水,再向该容器注水,多出的水会流入河流。
  现在给 q q q 组询问,对于每组询问,会得到一个时间 t t t,问最少打开多少个管道,才能在 t t t 时间内将所有的容器注满。

题目解析

  就不能看着那个图想这道题。。因为我想着想着就变成因为高度,可能存在一些情况,前面的水留不到后面了,但实际上,不管它们什么形状,题目只表明水会从 i i i 转移到 i + 1 i+1 i+1,因此我们只需要顺序的想就可以了。(其实考虑高度也可以做,用单调栈分成几段,再按下面的方法处理每一段就行)
  首先,如果只开一个管子,显然我们要开第一个。如果开第二个,加速让水注满,那么开第二个显然是更优的,因为同样是开两个管子,我们注水影响的容器数量变多了,但相对于总容量,我们的注水速度没变。那么如果要开 k k k 个管子,我们肯定要开前 k k k 个。
  如果我们知道前 k k k 个管子打开,需要多少时间才能注满全部的容器,那么我们就可以用二分处理每一个询问了。那么我们很容易想到,打开管子的容器和没有打开管子的容器需要分开讨论,如果前面的容器注满水,那么后面容器何时被注满水也只是简单计算,因此,前几个容器何时全部被注满水是必须讨论的点。
  设使用前 i i i 个管子注满前 i i i 个容器所需时间为 d p i dp_i dpi,容量的前缀和为 p r e i pre_i prei,首先直接计算 d p i dp_i dpi 是不行的,因为我们无法保证前面的所有容器均可以被注满。那么我们可以用动态规划去保留能够让 i − 1 i-1 i1 注满水的性质:
d p i = max ⁡ { d p i − 1 , ⌈ p r e i i ⌉ } dp_i = \max \{dp_{i-1}, \lceil \frac{pre_i }{ i}\rceil \} dpi=max{dpi1,iprei}
  能够这样做是因为,若 ⌈ p r e i i ⌉ > d p i − 1 \lceil \frac{pre_i }{ i}\rceil > dp_{i-1} iprei>dpi1,说明在 ⌈ p r e i i ⌉ \lceil \frac{pre_i }{ i}\rceil iprei 的时间下,首先满足 i − 1 i-1 i1 个容器被充满,之后我们就可以填第 i i i 个容器了;反之则说明只开前 i − 1 i-1 i1 个容器打开,足够填充前 i i i 个容器了。
  在知道了 d p i dp_i dpi 之后,类似地,我们就可以知道打开前 i i i 个管子,需要多少时间才能注满所有容器了:
max ⁡ ( d p i , ⌈ p r e n i ⌉ ) \max(dp_i, \lceil \frac{pre_n}{i} \rceil) max(dpi,ipren)
  之后我们直接用二分来找到满足条件的最小管子数量就可以了。
  这种取最大值保留性质的问题非常经典,在这个问题中,只有时间一个维度,因此在保留前面容量充满和前面容器加当前容器充满中选最大的一个就可以同时满足它们的要求。这种思路常见有趣,是一种经典问题。

Code

#include <bits/stdc++.h>
using namespace std;

typedef long long LL;
const int maxn = 2e5 + 7;
LL v[maxn], p[maxn];

int main() {
   int n, q, t;
   cin >> n;
   for(int i=1; i<=n; ++i)
       cin >> v[i], v[i] += v[i-1];
   for(int i=1; i<=n; ++i)
       p[i] = max((v[i] + i - 1) / i, p[i-1]);
   for(int i=1; i<=n; ++i)
       p[i] = max(p[i], (v[n] + i - 1) / i);
   reverse(p+1, p+n+1);

   cin >> q;
   while(q--) {
       cin >> t;
       int idx = upper_bound(p+1, p+n+1, t) - p - 1;
       idx = n - idx + 1;
       if (idx > n) cout << -1 << endl; else cout << idx << endl;
   }
   return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值