题解:CF1918D(D. Blocking Elements)
一、 读题
1. 题目链接
(1) 洛谷链接
(2) 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。
注意二分的边界是所有 a i a_i ai 相加之和,因为无论怎么选,最终的代价都是由若干个 a i a_i ai 相加(不会有重复),那么最大的①或者②都是从 a 1 a_1 a1 加到 a n a_n an 。
五、 代价
本题时间复杂度如下:
设
V
=
∑
i
=
1
n
a
i
V=\sum_{i=1}^na_i
V=∑i=1nai
(
二分答案
)
⋅
(
动态规划验证
)
=
O
(
l
o
g
(
V
)
)
⋅
(
遍历状态
)
⋅
(
转移
)
=
O
(
l
o
g
(
V
)
)
⋅
O
(
n
)
⋅
O
(
1
)
)
=
O
(
n
⋅
l
o
g
(
V
)
)
(二分答案)·(动态规划验证)=O(log(V))·(遍历状态)·(转移)=O(log(V))·O(n)·O(1))=O(n·log(V))
(二分答案)⋅(动态规划验证)=O(log(V))⋅(遍历状态)⋅(转移)=O(log(V))⋅O(n)⋅O(1))=O(n⋅log(V)),可以AC。
六、 编码
··
#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;
}