有点复杂的斜率优化题。
前置知识:
∑i=1n∑j=i+1nai∗aj=2(∑i=1nai)2−∑i=1nai2 (1)
证明:
方法1:
ab=2(a+b)2−a2−b2
ab+ac+bc=2(a+b+c)2−a2−b2−c2
……………………
方法2:
(1)式等价于∑i=1n∑j=1nai∗aj=(∑i=1ai)∗(∑i=1ai)
很明显,这是正确的。因为ai∗aj就相当于从左边括号取一个数乘上右边括号的一个数。
思路:
题目要求的是把n个数分成连续的m+1段的最小费用,每一段的费用等于段内每个数两两相乘的和。
设f[i][j]表示把前i个数分成j段的最小费用,a[i]为第i个数,s为a的前缀和,ss[i]=∑j=1ia[i]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]+2−2s[i]∗s[k]+s[i]2−ss[i]+s[k]2+ss[k]
化成 y = k x + b 的形式
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
",f[n][v]);
}
}