CF1918D Blocking Elements 题解

CF1918D Blocking Elements

如果你做过以下两道题目,那么这道题对你来说会简单得多。

P1182 数列分段 Section II

P2034 选择数字

由于题目要求最大值的最小值,考虑二分。

如果我们使用 x 的费用划分了整个数列,那么我们同样也可以使用比 x 更多的费用划分这个数列,因为划分方式不用进行改变。所以满足二分条件,可以二分。

考虑二分之后如何让判断是否可以划分整个序列。当我们选择隔断一个数时,会对已经选出的数的总和造成影响。由于这个影响,我们发现很难对这个问题进行贪心判定,所以考虑动态规划。

由于一般的动态规划方式很难对上一个隔断的位置进行维护,所以需要状态中包含位置信息。有一种经典的解决方式,设状态 dp[i] 表示隔断第 i 个元素,使前 i1 个元素满足条件的最小隔断元素之和,这样每个状态就包含了位置信息。然而,由于最后一个元素并不一定要被隔断,所以统计答案时要将所有在 i 处隔断后,不再进行隔断,最后一段之和小于当前二分答案值的位置 dp[i] 都统计进去,求最小值。

ai 表示数列中的 i 个元素的值,si 为数列 a 的前缀和,k 为二分当前值。根据状态定义,很容易推出以下转移方程:

dp[i]=min(dp[j]+ai)(j<i,si1sjk)

经过观察,我们发现每次可以转移的状态是根据位置减少的,且每次补充一个新的元素,这是很经典的单调队列优化动态规划的应用模型。使用单调队列维护可转移集合,即可做到 O(n) 求出所有 dp[i] 的值。

于是,我们就以 O(nlogn) 的时间复杂度解决了这个问题。

#include <bits/stdc++.h>
using namespace std;
long long t,n,a[300000],s[300000],f[300000];
long long v[300000],p[300000],q=1,h=0;
bool check(long long now)
{
	long long ans=1e15;
	for(int i=1;i<=n;i++)f[i]=1e15;
	q=1,h=0;
	v[++h]=0,p[h]=0;
	for(int i=1;i<=n;i++)
	    {
	    	while(s[i-1]-p[q]>now&&q<=h)q++;
	    	f[i]=v[q]+a[i];
	    	while(f[i]<=v[h]&&q<=h)h--;
	    	v[++h]=f[i],p[h]=s[i];
		}
	for(int i=n;i>=0;i--)
	    {
	    	if(s[n]-s[i]>now)break;
	    	ans=min(ans,f[i]);
		}
	return ans<=now;
}
 
int main()
{
	scanf("%lld",&t);
	while(t--)
	   {
	   	scanf("%lld",&n);
	   	for(int i=1;i<=n;i++)scanf("%lld",&a[i]);
	   	for(int i=1;i<=n;i++)s[i]=s[i-1]+a[i];
	   	long long l=1,r=1e15,ans=0;
	   	while(l<=r)
	   	   {
	   	   	long long mid=(l+r)>>1;
	   	   	if(check(mid))ans=mid,r=mid-1;
	   	   	else l=mid+1;
		   }
		printf("%lld\n",ans);
	   }
	return 0;
} 
posted @   w9095  阅读(1)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
点击右上角即可分享
微信分享提示