题解:CF1918D(D. Blocking Elements)
一、 读题
1. 题目链接
(1) 洛谷链接
洛谷链接
(2) CF链接
CF链接
2. 题意简述
已知一个长度为 n n n 的数组 a a a,构造一个数组 b b b(记其长度为 m m m),使得代价最小。代价为①和②的最大值。
① = ∑ i = 1 n a b i ①=\sum_{i=1}^{n}a_{b_i} ①=i=1∑nabi
② = max 0 ⩽ i ⩽ m ∑ j = b i + 1 b i − 1 + 1 a j ②=\max_{0\leqslant i\leqslant m}\sum_{j=b_{i}+1}^{b_{i-1}+1}a_j ②=0⩽i⩽mmaxj=bi+1∑bi−1+1aj
这里,我们规定 b 0 = b n + 1 = 1 b_0=b_{n+1}=1 b0=bn+1=1。
二、 分析
本题 n n n 为 1 0 5 10^5 105 数量级,是标准的 O ( n ⋅ l o g 2 ( n ) ) O(n·log_{2}(n)) O(n⋅log2(n)) 题。
三、 知识
二分答案+动态规划+前缀和+单调队列优化。
四、 思路
由于本题需要同时保证两个量(①和②)最优,所以一定是确定其中一个,并用已知的这一个去求未知的。我们不难想到,可以已知②求①。这里我们利用可行性进行二分答案,即通过 j u d g e judge judge 函数判断在保证②最大为 n u m num num 的情况下能得到的最小的①是否比 n u m num num 小,用二分答案找到“行”与“不行”的分界点。
那么如何验证呢?
我们不难想到动态规划——定义 f i f_{i} fi 为考虑 a a a 中的前 i i i 个数,保证②不超过 n u m num num,并且选择了 a i a_i ai,在这种情况下①的最小值。
转移也不难,对于 f i f_{i} fi,我们在从 1 1 1 到 i − 1 i-1 i−1 的范围内选择一个 j j j,使得从 ∑ k = j i a k \sum_{k=j}^ia_k ∑k=jiak 不超过 n u m num num,这样 f i f_i fi 就可以从所有 f j f_j fj 加上 1 1 1 转移,当然这里是求最小值。但是遍历的代价是平方级的,所以这里用前缀和+单调队列优化。
为了方便计算和统计答案,我们定义 a 0 = a n + 1 = 0 a_0=a_{n+1}=0 a0=an+1=0,这样显然不会影响最终的结果。现在要考虑初始值(边界值)。显然, f 0 = 0 f_0=0 f0=0。最终的答案(这里指的是验证中①的最小值)就是 f n + 1 f_{n+1} fn+1,这样既考虑了 1 1 1 至 n n n 的所有数,有没有必须选择 1 1 1 至 n n n 中某一个的限定。
验证的返回值就是最小的①是否不超过 n u m num num。
五、 编码
··
#include<bits/stdc++.h>
#define N 110000
using namespace std;
long long f[N]={},s[N]={};
int a[N]={},q[N]={},n=0,t=0;
bool judge(long long num);
int main(){scanf("%d",&t);while(t--){scanf("%d",&n);a[0]=0;s[0]=0;for(int i=1;i<=n;i++){scanf("%d",&a[i]);s[i]=s[i-1]+a[i];}a[n+1]=0;s[n+1]=s[n];long long l=0,r=s[n]+1;while(l+1<r){long long mid=(l+r)/2;if(judge(mid)==false){l=mid;}else{r=mid;}}printf("%lld\n",r);}return 0;
}
bool judge(long long num){for(int i=1;i<=n+1;i++){f[i]=0;}int front=1,rear=0;rear++;q[rear]=0;f[0]=0;for(int i=1;i<=n+1;i++){while(front<=rear&&s[i-1]-s[q[front]]>num){front++;}f[i]=f[q[front]]+a[i];while(front<=rear&&f[q[rear]]>f[i]){rear--;}rear++;q[rear]=i;}if(f[n+1]<=num){return true;}return false;
}