题意:
\[\begin{aligned}&\text{给定长度为 } n \text{ 的数列 } \{a_i\} \text{ 和两个参数 } k, s \text{,将 } \{a_i\} \text{ 划分为 } k \text{ 段,最大化和} \geq s \text{ 的段数。} \\
&k\le n\le 2.5e5
\end{aligned}
\]
思路
有划分为恰好 \(k\) 段的限制,我们可以考虑wqs二分。发现直接将答案作为 \(y\) 轴是没有单调性的,因此只能换一下。
发现由于我们要让有贡献的段数尽量大,因此要让没有贡献的段长尽量小,因此没有贡献的段长直接设为1。
然后就又发现我们要让有贡献的段数尽量大,也要让有贡献的段长尽量小。
因此我们设 \(f(x)\) 为总贡献为 \(x\) 时有贡献的段的总长度。然后发现当可以划分的合法序列的段数大于 \(k\) 的时候,一定可以通过某种合并方式使得这个序列的划分的段数变为 \(k\) 同时仍然合法。
因此对于一个序列,其合法的限制条件就是 \(k\le x+n-f(x)\),即 \(\sum{r-l}\le n-k\)。
然后就将 \(sum{r-l}\) 作为 \(y\) 轴,二分的总贡献 \(x\) 作为 \(x\) 轴,直接wqs二分即可。
code
需要注意同样需要特殊判定多个点在同一条直线上的情况。
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=3e6+7;
int n,K,s,a[N],sum[N],pre[N];
pair <int,int> f[N];
bool check(int k){for(int i=1;i<=n;i++){f[i]=f[i-1];if(pre[i]>=1) f[i]=min(f[i],{f[pre[i]-1].first+i-pre[i]-k,f[pre[i]-1].second+1});}return (f[n].first+f[n].second*k<=n-K);
}
signed main(){ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);cin>>n>>K>>s;for(int i=1;i<=n;i++) cin>>a[i],sum[i]=sum[i-1]+a[i];for(int i=1,loc=0;i<=n;i++) {while(sum[i]-sum[loc-1]>=s) loc++;pre[i]=loc-1;}int l=1,r=n,ansk=l;while(l<=r){int mid=(l+r)>>1;if(check(mid)) ansk=mid,l=mid+1;else r=mid-1;}int eee=check(ansk),x=min(f[n].second,K),y=f[n].first+f[n].second*ansk;while(y+ansk<=n-K&&x<K) x++,y+=ansk;cout<<x;return 0;
}