https://blog.csdn.net/a_forever_dream/article/details/105581221
https://blog.csdn.net/a_forever_dream/article/details/105587292
#include <cstdio>
#include <cstring>
#define maxn 3010
#define mid (l+r>>1)
int n,m,a[maxn],sum[maxn],ans;
int l=0,r=1000000;
//贡献
int w(int l,int r)
{
// int mid = l+r >>1;
//在l 到 r 之间放的贡献
//l r
// mid
//
return sum[r]-sum[mid]-(r-mid)*a[mid]+(mid-l)*a[mid]-(sum[mid-1]-sum[l-1]);
}
struct node
{
int pos,l,r;
};//pos统治区间[l,r]
node q[maxn];
int f[maxn],s[maxn],t;
int calc(int from,int now,int cost)
{
return from>now?999999999:f[from]+w(from+1,now)+cost;
}
int solve(int cost)
{
q[t=1]= {0,1,n};
for(int i=1; i<=n; i++)
{
int l=1,r=t,pos;
while(l<=r)
q[mid].l<=i?(l=(pos=mid)+1):(r=mid-1);
//找到统治i的点,找到i在哪个区间内
f[i]=calc(q[pos].pos,i,cost);
s[i]=s[q[pos].pos]+1;
pos=-1;//从最后一个区间看起,看看i能统治多少个区间
while(t>0&&calc(i,q[t].l,cost)<=calc(q[t].pos,q[t].l,cost))pos=q[t--].l;
//最后那个不能全部统治的区间二分一下,能统治多少是多少
if(t>0&&calc(i,q[t].r,cost)<=calc(q[t].pos,q[t].r,cost))
{
l=q[t].l,r=q[t].r;
while(l<=r)calc(i,mid,cost)<=calc(q[t].pos,mid,cost)?(r=(pos=mid)-1):(l=mid+1);
q[t].r=pos-1;
}
if(pos!=-1)q[++t]= {i,pos,n}; //加上i统治的新区间
}
return s[n];
}
int main()
{
//n个数字,分成m段
scanf("%d %d",&n,&m);
for(int i=1; i<=n; i++)
scanf("%d",&a[i]),sum[i]=sum[i-1]+a[i];
//二分斜率
while(l<=r)
{
//以斜率为mid的直线去切,当与y轴截距最大的时候,就是切线
//此时得到的横坐标,如果大于等于m,就可以更新,
if(solve(mid)>=m)
ans=f[n]-m*mid,l=mid+1;
//如果小于等于m了,那么就说明斜率太大了,该变小,然后让横坐标右移
else
r=mid-1;
}
printf("%d",ans);
}