题意
给出一个长度为n的序列a[1..n],现在要将这个序列分成k段,第i段
[li,ri]
[
l
i
,
r
i
]
的贡献为
(a[ri]−a[li])2
(
a
[
r
i
]
−
a
[
l
i
]
)
2
。要求最小化每一段贡献的和。
n<=10000,k<=100
分析
设
dp[i,j]
d
p
[
i
,
j
]
表示前i个位置分成j段的最小贡献和。
不难得到转移:
将平方拆开后得
如果我们把这个转移看成是一个点在一堆直线里面纵坐标的最小值,不难发现答案一定在这些直线所形成的凸壳上。
我们可以cdq分治,每次先递归[l,mid],把[l,mid]的凸壳建出来,转移给[mid+1,r],然后往下递归[mid+1,r]即可。
代码
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#define MIN(x,y) x=min(x,y)
using namespace std;
typedef long long LL;
const int N=10005;
const LL inf=(LL)1e17;
int n,m,a[N],que[N];
LL f[N][105];
struct line{LL k,b;}p[N];
bool cmp(line a,line b)
{
return a.k>b.k||a.k==b.k&&a.b<b.b;
}
double get_pts(line x,line y)
{
return (double)(y.b-x.b)/(x.k-y.k);
}
void solve(int l,int r)
{
if (l==r) return;
int mid=(l+r)/2;
solve(l,mid);
for (int j=1;j<m;j++)
{
int tot=0;
for (int i=l;i<=mid;i++) p[++tot].k=-2*a[i+1],p[tot].b=(LL)a[i+1]*a[i+1]+f[i][j];
sort(p+1,p+tot+1,cmp);
int top=0;
for (int i=1;i<=tot;i++)
{
if (i>1&&p[i].k==p[i-1].k) continue;
while (top>1&&get_pts(p[i],p[que[top-1]])<=get_pts(p[que[top]],p[que[top-1]])) top--;
que[++top]=i;
}
for (int i=mid+1;i<=r;i++)
{
int L=1,R=top-1;
while (L<=R)
{
int mid=(L+R)/2;
if ((LL)p[que[mid+1]].k*a[i]+p[que[mid+1]].b<(LL)p[que[mid]].k*a[i]+p[que[mid]].b) L=mid+1;
else R=mid-1;
}
MIN(f[i][j+1],(LL)p[que[L]].k*a[i]+p[que[L]].b+(LL)a[i]*a[i]);
}
}
solve(mid+1,r);
}
int main()
{
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++) scanf("%d",&a[i]);
for (int i=1;i<=n;i++)
for (int j=1;j<=m;j++)
f[i][j]=inf;
for (int i=1;i<=n;i++) f[i][1]=(LL)(a[i]-a[1])*(a[i]-a[1]);
solve(1,n);
printf("%lld",f[n][m]);
return 0;
}