
求解思路
1.先把目标写出来
因为要形成一个等差数列,设新形成的等差数列为{bi}
设首项为b1,则bi=b1+(i-1)*d
求需要增加多少满足该序列成为一个公差非正的等差数列
那么增加的数 sum
sum=∑i=1n(b i −a i )\sum_{i=1}^{n} (b~i~-a~i~)∑i=1n(b i −a i )=∑i=1n(b i )\sum_{i=1}^{n} (b~i~)∑i=1n(b i ) ---∑i=1n(a i )\sum_{i=1}^{n} (a~i~)∑i=1n(a i )
∑i=1nb i \sum_{i=1}^{n} b~i~∑i=1nb i =nb1+ n *(n-1)*d/2
2.发现关键点
发现,sum的结果与bi相关,即与公差d与首项b1相关
在这个式子中,有两个未知数,尝试用一个来表示另一个
尝试用公差d表示首项b1
∵ bi≥ai
又∵ bi=b1+(i-1)*d
∴ b1+(i-1)*d≥ai
即b1≥ max(ai - (i-1)*d)
b1是max(ai - (i-1)*d)的最大值
sum=∑i=1n(b i −a i )\sum_{i=1}^{n} (b~i~-a~i~)∑i=1n(b i −a i )
=∑i=1n(b i )\sum_{i=1}^{n} (b~i~)∑i=1n(b i ) ---∑i=1n(a i )\sum_{i=1}^{n} (a~i~)∑i=1n(a i )
=n * max(ai - (i-1)d)+n*(n-1)*d/2-∑i=1n(a i )\sum_{i=1}^{n} (a~i~)∑i=1n(a i )
该式子中包含三项
- 中间项n*(n-1)*d/2是一个一次函数
- 尾项 ∑i=1n(a i )\sum_{i=1}^{n} (a~i~)∑i=1n(a i )是常数项
- 首项=n * max(ai - (i-1)d),其中每一个ai-(i-1)*d是一次函数。
我们暂时不考虑d的范围,即斜率为正还是为负的情况,画出下面的图,即画出多条一次函数,取每一个d点在该图上的最大值
对于每一个d点在各个函数上的最大值,发现他是一个凸函数。
而剩下的一次函数和常数项,不会影响函数的凸凹性。

3.找到解决方法
在凸函数上,斜率非正的部分取最小值,可以用三分求解。
代码实现
cpp
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
long long sum,n;
int a[N];
long long cul(long long d)
{
long long shouxiang=-1e18;
for(int i=1;i<=n;i++)
{
shouxiang=max(shouxiang,a[i]-(i-1)*d);
}
return n*shouxiang+n*(n-1)*d/2-sum;
}
int main()
{
int T;
cin>>T;
while(T--)
{
cin>>n;
sum=0;
for(int i=1;i<=n;i++)
{
cin>>a[i];
sum+=a[i];
}
long long l=-1e9,r=0;
while(r>l)
{
long long ll=l+(r-l)/3;
long long rr=r-(r-l)/3;
if(cul(ll)<=cul(rr))
{
r=rr-1;
}
else l=ll+1;
}
cout<<cul(l)<<endl;
}
return 0;
}
我的bug
cpp
return n*shouxiang+n*(n-1)*d/2-sum;
这里n原本是int 类型,n*(n-1)会爆
三分模板
cpp
l=0.0,r=1000.0;
while(r-l>eps)
{
k=(r-l)/3;
mid1=l+k;
mid2=r-k;
if(f(mid1)>f(mid2)) l=mid1;
else r=mid2;
}
printf("%.4lf\n",f(l));
这是我之前写的三分模板
在本题中我刚开始写的三分是
cpp
while(r>l)
{
long long k=(r-l)/3;
long long ll=l+k;
long long rr=r-k;
if(cul(ll)>cul(rr))
{
l=ll;
}
else r=rr;
}
cout<<cul(l)<<endl;
如果 cul(ll) > cul(rr),说明最小值在右边,应该 l = ll + 1
否则最小值在左边,应该 r = rr - 1
这种写法当 ll 和 rr 相邻时,l 和 r 可能不变,造成死循环
改进为:
cpp
if(cul(ll)>cul(rr))
{
l=ll+1;
}
else r=rr-1;
问题 2:循环条件 while(r > l) 太宽松
当 r == l + 1 时,k = (r-l)/3 = 0,那么:
ll = l + 0 = l
rr = r - 0 = r
然后 cul(l) > cul( r) 时 l = l,否则 r = r,区间完全不变 → 死循环。
还可以这么写
cpp
while(r - l > 3)
{
long long k = (r - l) / 3;
long long ll = l + k;
long long rr = r - k;
if(cul(ll) > cul(rr))
l = ll; // 这里可以不用 +1,因为 ll 和 rr 距离较远
else
r = rr;
}
// 暴力枚举剩余区间
long long ans = INF;
for(long long d = l; d <= r; d++)
ans = min(ans, cul(d));
cout << ans << '\n';