有点复杂的斜率优化题。
前置知识:
∑i=1n∑j=i+1nai∗aj=(∑i=1nai)2−∑i=1nai22 (1)\sum_{i=1}^n \sum_{j=i+1}^n a_i*a_j=\dfrac{(\sum_{i=1}^n a_i)^2- \sum_{i=1}^n a_i^2}{2}~~~~~~~~ (1)∑i=1n∑j=i+1nai∗aj=2(∑i=1nai)2−∑i=1nai2 (1)
证明:
方法1:
ab=(a+b)2−a2−b22ab=\dfrac{(a+b)^2-a^2-b^2}{2}ab=2(a+b)2−a2−b2
ab+ac+bc=(a+b+c)2−a2−b2−c22ab+ac+bc=\dfrac{(a+b+c)^2-a^2-b^2-c^2}{2}ab+ac+bc=2(a+b+c)2−a2−b2−c2
………………………………………………………………
方法2:
(1)式等价于∑i=1n∑j=1nai∗aj=(∑i=1ai)∗(∑i=1ai)\sum_{i=1}^n \sum_{j=1}^n a_i*a_j =(\sum_{i=1}a_i)*(\sum_{i=1}a_i)∑i=1n∑j=1nai∗aj=(∑i=1ai)∗(∑i=1ai)
很明显,这是正确的。因为ai∗aja_i*a_jai∗aj就相当于从左边括号取一个数乘上右边括号的一个数。
思路:
题目要求的是把n个数分成连续的m+1段的最小费用,每一段的费用等于段内每个数两两相乘的和。
设f[i][j]表示把前i个数分成j段的最小费用,a[i]为第i个数,s为a的前缀和,设f[i][j]表示把前i个数分成j段的最小费用,a[i]为第i个数,s为a的前缀和,设f[i][j]表示把前i个数分成j段的最小费用,a[i]为第i个数,s为a的前缀和,ss[i]=∑j=1ia[i]2ss[i]=\sum_{j=1}^i a[i]^2ss[i]=∑j=1ia[i]2。
则有:f[i][j]=min f[k][j−1]+(s[i]−s[k])2−(ss[i]−ss[k])2f[i][j]=min~~~f[k][j-1]+\dfrac{(s[i]-s[k])^2-(ss[i]-ss[k])}{2}f[i][j]=min f[k][j−1]+2(s[i]−s[k])2−(ss[i]−ss[k])
f[i][j]=f[k][j−1]+−2s[i]∗s[k]+s[i]2−ss[i]+s[k]2+ss[k]2~~~~~~~~~f[i][j]=f[k][j-1]+\dfrac{-2s[i]*s[k]+s[i]^2-ss[i]+s[k]^2+ss[k]}{2} f[i][j]=f[k][j−1]+2−2s[i]∗s[k]+s[i]2−ss[i]+s[k]2+ss[k]
化成 y = k x + b 的形式~~~~~~~~~~~~y~~~~~~~~~~~~~~~~~~~~~~=~~~k~~~~~x~~~+~~~~~~~~~~b~~~~~~~~~~~~~~~~~~~~~~的形式 y = k x + b 的形式
f[k][j−1]+ss[k]+s[k]22=s[i]∗s[k]+f[i][j]−s[i]2−ss[i]2f[k][j-1]+\dfrac{ss[k]+s[k]^2}{2}=s[i]*s[k]+f[i][j]-\dfrac{s[i]^2-ss[i]}{2}f[k][j−1]+2ss[k]+s[k]2=s[i]∗s[k]+f[i][j]−2s[i]2−ss[i]
斜率、横坐标单调递增,就可做了。
代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N=1010;
int n,m,q[N],l,r,u,v;
ll f[N][2],s[N],ss[N];
ll x(int i){return s[i];}
double y(int i){return f[i][v]+0.5*(s[i]*s[i]+ss[i]);}
bool pd(int i,int j,int k)
{
return (y(j)-y(i))*(x(k)-x(j))<(y(k)-y(j))*(x(j)-x(i));
}
#define g getchar()
template<class o>
void qr(o&x)
{
char c=g;x=0;
while(!('0'<=c&&c<='9'))c=g;
while('0'<=c&&c<='9')x=x*10+c-'0',c=g;
}
int main()
{
while(1)
{
qr(n);qr(m);
if(!n&&!m)return 0;m++;
for(int i=1;i<=n;i++)
qr(s[i]),ss[i]=s[i]*s[i]+ss[i-1],s[i]=s[i]+s[i-1];
for(int i=1;i<=n;i++)
f[i][0]=(s[i]*s[i]-ss[i])>>1;
u=1;v=0;
for(int j=2;j<=m;j++,swap(u,v))
{
l=r=1;q[l]=j-1;
for(int i=j,k;i<=n;i++)
{
while(l<r&&y(q[l+1])-y(q[l])<=s[i]*(x(q[l+1])-x(q[l])))l++;
k=q[l];
f[i][u]=f[k][v]+((1LL*(s[i]-s[k])*(s[i]-s[k])-ss[i]+ss[k])>>1);
while(l<r&&!pd(q[r-1],q[r],i))r--;
q[++r]=i;
}
}
printf("%lld\n",f[n][v]);
}
}