Subsegments with Large Sums 题解

一个你看起来非常简单实际上并不简单的题。

前置知识

二分,wqs二分(不会也没关系这是今天的主题),dp

本题思路

对于恰好为 \(k\) 的限制我们可以考虑wqs二分,但是直接用 wqs需要有前提条件,比如最关键的这个函数要满足凸/凹性。显然有的一件事,就是如果将定义为答案要求的函数,如 \(f_x\) 表示恰好分成 \(x\) 个区间最大化的 \(\ge s\) 的段数。我们发现

这个函数不是凸的,因为斜率的变化不一定。

所以不能直接做,我们先考虑转化题意,首先我们二分最后的答案将题目转化为判定(单调性显然),所以现在要求在钦定有 \(x\) 个区间 \(\ge s\) 最多能分成多少区间设为 \(f_x\) (与 \(k\) 比较即可判定) 。但是这个函数我们仍然不好描述,所以我们换一种描述,变为设 \(g_x\) 表示 \(\sum_{i}^{len}r_i-l_i\)\(len\)表示区间个数表示区间所有区间的右减左)的最小值。找到这个我们就能找到 \(f(x)\)

\[\begin{aligned} \sum_{i=1}^{len} r_i-l_i+1=n\\ \sum_{i=1}^{len} r_i-l_i=n-len\\ \end{aligned} \]

所以我们有了 \(g\) 最小就是 \(f\) 最大。然后因为 \(g\) 具有凸性所以可以上 wqs二分dp即可找出 \(g\)

wqs的应用

个人感觉是求一个有特殊性质的函数,我们没有简易的表达式来快速计算,但是在扔掉一些限制会非常好求。
对于这里恰好选 \(x\) 个区间求最小值是不好做的(dp不好保证恰好 \(k\)),但是如果没有限制,就可以直接上 \(dp\) 求。

node solve(int x)
{
	for(int i=1;i<=n;i++)
	{
		f[i]=f[i-1];
		if(pu[i])f[i]=min(f[i],{f[pu[i]-1].fi+i-pu[i]-x,f[pu[i]-1].se+1});
	}
	return f[n];
}

(理解方式一:带权)所以我们二分一个值作为做贡献的区间的附加值,使得在选择过程中调整值来调整选择的个数直到求出对应 \(x\) 的值,就结束了。如在这个题我们想要多选 \(\ge s\) 的区间就是让这个区间代价变小变为 \(r-l-k\) ,同理变少就是让代价变大。所以我们就可以写出刚才的代码
(理解方式二:图像)我们在二分斜率求与图像相切的时候的交点(下凸包),就是求给出 \(k\) 的时候最小的 \(g(x)-kx\),写的 \(dp\) 的过程中的第一维就是 \(g(x)-xk\) 第二维就是 \(x\) ,最后得到的就 \(g(x)-kx\) 的最小值和它的值。

代码

点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N=3e5+10;
int a[N],n,k,s,pu[N];
struct node
{
	int fi,se;
}f[N];
bool operator<(const node&x,const node&y)
{
	if(x.fi!=y.fi)return x.fi<y.fi;
	else return x.se<y.se; 
}
node solve(int x)
{
	for(int i=1;i<=n;i++)
	{
		f[i]=f[i-1];
		if(pu[i])f[i]=min(f[i],{f[pu[i]-1].fi+i-pu[i]-x,f[pu[i]-1].se+1});
	}
	return f[n];
}
bool check(int x)
{
	int l=0,r=n,ans=-1;//二分斜率
	while(l<=r)
	{
		int mid=(l+r)>>1;
		if(solve(mid).se<=x)l=mid+1,ans=mid;
		else r=mid-1; 
	} 
	return solve(ans).fi+x*ans<=n-k;
}
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	cin>>n>>k>>s;
	for(int i=1;i<=n;i++)cin>>a[i],a[i]+=a[i-1];
	int j=1;
	for(int i=1;i<=n;i++)
	{
		while(a[i]-a[j-1]>=s)j++;
		pu[i]=j-1;//存最靠近的j 
	}
	int l=0,r=k,ans=-1;//二分最终答案
	while(l<=r)
	{
		int mid=(l+r)>>1;
		if(check(mid))l=mid+1,ans=mid;
		else r=mid-1;	
	} 
	cout<<ans<<'\n';
	return 0;
} 
posted @ 2025-03-25 14:57  exCat  阅读(8)  评论(0)    收藏  举报