斜率DP的入门题。
题意很清楚,就是输出序列a[n],把连续的单词放在同一行输出的费用是连续输出的单词费用和的平方加上常数M
一行的费用为 cost=(∑C[i])2+M
让我们求这个费用的最小值。
设 Si 为 Ci 的前缀和。
设dp[i]表示输出前i个单词的最小费用,那么有如下的DP方程:
dp[i]=min(dp[j]+(Si−Sj)2)+M (0<j<i)
我们首先假设在算
dp[i]
时,
k<j
,
j
点比
也就是 dp[j]+(Si−Sj)2<=dp[k]+(Si−Sk)2;
所谓
j
比
对上述方程进行整理很容易得到:
(dp[j]+S2j)−(dp[k]+S2k)(Sj−Sk)<=2∗Si
注意整理中要考虑下正负,涉及到不等号的方向。
左边我们发现如果令:
yj=dp[j]+S2j
xj=Sj
那么就变成了斜率表达式: yj−ykxj−xk<=sum[i];
而且不等式右边是递增的。
所以我们可以看出以下两点:我们令 g[k,j]=yj−ykxj−xk
第一:如果上面的不等式成立,那就说
j
比
第二:如果
k
<
假设
相反如果
所以这样相当于在维护一个下凸的图形,斜率在逐渐增大。
通过一个队列来维护。
#include <bits/stdc++.h>
using namespace std;
#define prt(k) cerr<<#k" = "<<k<<endl
typedef long long ll;
const ll inf = 0x3f3f3f3f;
const int N = 505000;
int M;
ll S[N];
int n;
ll dp[N];
ll Y(int j)
{
return dp[j] + S[j] * S[j];
}
ll X(int j)
{
return S[j];
}
bool cmp(int k, int j, int i) // j tao tai
{ // k < j < i
return (Y(j) - Y(k)) * (X(i) - X(j)) >=
(Y(i) - Y(j)) * (X(j) - X(k));
}
int q[N];
ll sqr(ll x)
{
return x * x;
}
int main()
{
while (scanf("%d%d", &n, &M)==2) {
S[0] = 0;
for (int i=1;i<=n;i++) {
int c; scanf("%d", &c);
S[i] = S[i-1] + c;
}
dp[0] = 0;
int head = 0, tail = -1;
q[++tail] = 0;
for (int i=1;i<=n;i++) {
while (tail - head + 1 >= 2 ) {
int k = q[head];
int j = q[head+1];
if (Y(j) - Y(k)<=2*S[i]*(X(j) - X(k))) {
head++;
}
else break;
}
int j = q[head];
dp[i] = dp[j] + sqr(S[i] - S[j]) + M;
while (tail - head + 1 >= 2) {
int k = q[tail - 1];
int j = q[tail];
if (cmp(k, j, i)) tail--;
else break;
}
q[++tail] = i;
}
printf("%I64d\n", dp[n]);
}
return 0;
}