题意:把一个序列分成m段, 使得每段的花费和最小. 某一段的花费等于这段里任意两个数字的乘积和.
看着就很斜率优化的题目. 假设
si
表示
∑ij=1aj
,
ci
表示i到j这一段的cost, 那么有
cost(i,j)=cj−ci−∑p=1j∑q=j+1iap∗aq=ci−cj−sj×(si−sj)
转移的式子是
fi,k=min{fj,k−1+costi,j+1∣∣j<i}
, 然后整理
j
比k(k≤j)优的条件(第二维表示之前已经有了多少个断点)
fj,p−1+ci−cj−sj×(si−sj)≤fk,p−1+ci−ck−sk×(si−sk)
整理一下
fj,p−1−cj−fk,p−1+cksj−sk≤si
这东西看着就很斜率, 然后就是一般的斜率优化了.
#include <bits/stdc++.h>
using namespace std;
#define maxn 1111
long long dp[maxn][maxn];
int n, m, que[maxn];
long long a[maxn], sum[maxn], cost[maxn];
long long up (int i, int j, int k) {
return dp[i][k]-cost[i]+sum[i]*sum[i] - (dp[j][k]-cost[j]+sum[j]*sum[j]);
}
long long down (int i, int j, int k) {
return sum[i]-sum[j];
}
int main () {
while (scanf ("%d%d", &n, &m) == 2 && n+m) {
sum[0] = 0;
for (int i = 1; i <= n; i++) scanf ("%lld", &a[i]), sum[i] = sum[i-1]+a[i];
cost[1] = 0;
for (int i = 2; i <= n; i++) cost[i] = cost[i-1]+sum[i-1]*a[i];
for (int i = 1; i <= n; i++) dp[i][0] = cost[i];
for (int k = 1; k <= m; k++) {
int L = 0, R = 0;
que[R++] = 0;
for (int i = 1; i <= n; i++) {
while (L+1 < R && up (que[L+1], que[L], k-1) <= sum[i]*down (que[L+1], que[L], k-1))
L++;
int j = que[L];
dp[i][k] = dp[j][k-1] + cost[i] - cost[j] - sum[j]*(sum[i]-sum[j]);
while (L+1 < R && up (i, que[R-1], k-1)*down (que[R-1], que[R-2], k-1) <=
up (que[R-1], que[R-2], k-1)*down (i, que[R-1], k-1))
R--;
que[R++] = i;
}
}
printf ("%lld\n", dp[n][m]);
}
return 0;
}