题解:CF1918D(D. Blocking Elements)

本文介绍了解决CF1918D问题的算法,涉及二分搜索、动态规划以及前缀和和单调队列的使用,以优化求解一个关于数组操作的最小代价问题。
摘要由CSDN通过智能技术生成

题解: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=1nabi
② = 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 =0immaxj=bi+1bi1+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(nlog2(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 i1 的范围内选择一个 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(nlog(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;
}
  • 26
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值