【简化题意】
一条长度为n的序列中选取k个数,并选取其中一个数作为基准数。其他k-1个数减去基准数后的平方和等于代价。求最小代价是多少。n<=1e5
输入时的序列不严格单调递增。
【分析】
首先我们改变一下思路,每次选取一个基准数,然后选择剩下的k-1个数。显然的这k-1个数一定是连续的,且分布在基准数的左右两边。并且随着基准数的位置右移单调递增。
利用two-pointer的思想我们用O(n)总时间确定每个基准数对应的最小区间,均摊上是O(1)。问题是如何快速区间内差值平方和。
设
x
i
为
上
一
个
区
间
中
编
号
为
i
的
元
素
和
基
准
值
的
差
值
。
设x_i为上一个区间中编号为i的元素和基准值的差值。
设xi为上一个区间中编号为i的元素和基准值的差值。
那
么
随
着
基
准
数
位
置
的
右
移
,
差
值
会
增
长
或
减
少
。
基
准
数
左
边
会
增
加
Δ
,
右
边
会
减
小
Δ
。
那么随着基准数位置的右移,差值会增长或减少。基准数左边会增加 \Delta ,右边会减小\Delta。
那么随着基准数位置的右移,差值会增长或减少。基准数左边会增加Δ,右边会减小Δ。
从
原
来
的
x
i
2
,
变
为
(
x
i
+
Δ
)
2
,
展
开
后
得
到
x
i
2
+
2
Δ
x
i
+
Δ
2
从原来的x_i^2,变为{(x_i+\Delta)}^2,展开后得到x_i^2+2\Delta x_i+\Delta^2
从原来的xi2,变为(xi+Δ)2,展开后得到xi2+2Δxi+Δ2
Δ
和
Δ
2
可
以
O
(
1
)
计
算
,
预
先
维
护
好
Σ
x
i
我
们
也
可
以
每
次
O
(
1
)
求
解
区
间
(
虽
然
好
像
常
数
极
大
)
\Delta 和\Delta^2可以O(1)计算,预先维护好\Sigma x_i我们也可以每次O(1)求解区间(虽然好像常数极大)
Δ和Δ2可以O(1)计算,预先维护好Σxi我们也可以每次O(1)求解区间(虽然好像常数极大)
更优的O(n)算法请关注liuzibujian的blog。
Code:
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long LL;
const LL maxn=1e5+1000;
LL pos,l,r,n,k,base;
LL suml=0,sumr=0,sum=0;
LL a[maxn],ans;
inline void read(LL &x){
x=0;char tmp=getchar();
while(tmp<'0'||tmp>'9') tmp=getchar();
while(tmp>='0'&&tmp<='9') x=(x<<1)+(x<<3)+tmp-'0',tmp=getchar();
}
inline LL pw(LL x){
return x*x;
}
int main(){
freopen("battle.in","r",stdin);
freopen("battle.out","w",stdout);
cin>>n>>k;
for(int i=1;i<=n;i++)
read(a[i]);
l=1,pos=1,r=k,base=a[1],suml=0;
for(int i=2;i<=k;i++)
sumr+=a[i]-base,sum+=pw(a[i]-base);
ans=sum;
while(pos<n){
LL d=a[pos+1]-a[pos];
sum=sum+2*d*suml+(pos-l+1)*d*d-2*d*sumr+(r-pos)*d*d;
suml+=(pos-l+1)*d,sumr-=(r-pos)*d;
pos++; base=a[pos];
while(pw(a[l]-base)>pw(a[r+1]-base)) sum=sum-pw(a[l]-base)+pw(a[r+1]-base),suml-=base-a[l],sumr+=a[r+1]-base,l++,r++;
ans=min(ans,sum);
}
printf("%lld\n",ans);
return 0;
}