对于一个有i,j,ij项的转移式,我们不能用单调队列去优化
我们可以把一个转移方程式化成一个一次函数解析式:
b代表只有i的项
y代表只有j的项
x代表有i且有j的项中含j的项
k代表有i且有j的项中含i的项
所以我们要求的就是b,也就是截距,这样我们可以用斜率维护一个凸包,每次把队头(也可以是栈,或者是二分)取出来计算答案(根据最开始的解析式),更新队尾(根据斜率)维护凸包
首先可以推出:f[i]=min(f[j]+(sun[i]-sum[j]-j+i-L-1)^2 (sum为输入的前缀和)
然后化:
设A[i]=sum[i]+i
B[i]=sum[i]+i+L+1
∴f[i]=f[j]+(a[i]-b[j])^2
f[i]=f[j]+a[i]^2+b[j]^2+2*a[i]*b[j]
(f[j]+b[j])=(2*a[i])*(b[j])+(f[i]+a[i]^2)
即y=kx+b
因为我们要求dp[i]最小,所以我们需要求一个斜率最小的线
#include<iostream>
using namespace std;
long double f[51419], sum[51419];int n, l;
double B(int j) { return sum[j] + j + 1 + l; }
double A(int i) { return sum[i] + i; }
double k(int i) { return 2 * A(i); }
double y(int j) { return f[j] + B(j)*B(j); }
double x(int j) { return B(j); }
double slope(int i, int j) { return (y(i) - y(j)) / (x(i) - x(j)); }
int q[51419];
int main() {
cin >> n >> l;
for (int i = 1; i <= n; i++) {
int a;
cin >> a;
sum[i] = (sum[i - 1] + a);
}
int l, r;
l = r = 1;
for (int i = 1; i <= n; i++) {
while (l < r && slope(q[l], q[l + 1]) < 2 * A(i)) ++l;//看看可不可以找到斜率比现在这根线小的
f[i] = f[q[l]] + (A(i) - B(q[l])) * (A(i) - B(q[l]));
while (l < r && slope(i, q[r - 1]) < slope(q[r - 1], q[r])) --r;//更新队列
q[++r] = i;
}
cout << long long(f[n]);
}