CF1918D Blocking Elements 题解

CF1918D Blocking Elements

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

P1182 数列分段 Section II

P2034 选择数字

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

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

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

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

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

\[dp[i]=\min(dp[j]+a_i)(j\lt i,s_{i-1}-s_{j}\le k) \]

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

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

#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 @ 2025-02-14 18:32  w9095  阅读(27)  评论(0)    收藏  举报