题解
使用dp求解,转移方程f[i] = min(f[i], f[j] + (s[i] - s[j] - l)^2),复杂度O(N^2),使用斜率优化。
j1 < j2 < i 在 j2比j1好的前提下 斜率方程
f[j2] + (s[i] - s[j2] - l)^2 < f[j1] + (s[i] - s[j1] - l)^2 将j放在左边i和常量放在右边
((f[j2] + s[j2]^2) - (f[j1] + s[j1]^2)) / (s[j2] - s[j1]) < 2(s[i]-L)
令x=(f[j] + s[j]^2), y=s[j]可以表示为点(y, x)计算斜率
当前位置j1没j2优则对于后面转移也不会用j1,可以直接将j1淘汰。
证: j1 < j2 < i < i2 令s[i2] = s[i] + v
f[j2] + (s[i] + v - s[j2] - l)^2 < f[j1] + (s[i] + v - s[j1] - l)^2 利用原式化为
-2vs[j2] < -2vs[j1] 由于前缀和非递减的证
AC代码
#include <stdio.h>
#include <bits/stdc++.h>
#define fst first
#define sed second
using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
const ll LINF = 0x3f3f3f3f3f3f3f3f;
const int N = 5e4 + 10;
ll f[N], s[N]; //f[i]前i项的最小代价 s[i]前i项的前缀和+i
int ls[N], head, tail; //维护斜率递增上凸壳
//f[i] = min(f[i], f[j] + (s[i] - s[j] - l)^2)
//j1 < j2 < i 在 j2比j1好的前提下 斜率方程
//f[j2] + (s[i] - s[j2] - l)^2 < f[j1] + (s[i] - s[j1] - l)^2 将j放在左边i和常量放在右边
//((f[j2] + s[j2]^2) - (f[j1] + s[j1]^2)) / (s[j2] - s[j1]) < 2(s[i]-L)
//令x=(f[j] + s[j]^2), y=s[j]可以表示为点(y, x)计算斜率
//j1 < j2 < i < i2 令s[i2] = s[i] + v
//f[j2] + (s[i] + v - s[j2] - l)^2 < f[j1] + (s[i] + v - s[j1] - l)^2 利用原式化为
//-2v*s[j2] < -2v*s[j1] 由于前缀和非递减则说明当前位置j1没j2优则对于后面转移也不会用j1
//可以直接将j1淘汰 如果不满足则需要维护凸壳二分查找
double Y(int j)
{
return f[j] + 1.0 * s[j] * s[j];
}
double X(int j)
{
return s[j];
}
double slope(int j1, int j2) //计算两点斜率
{
//return (f[j2] + 1.0 * s[j2] * s[j2] - f[j1] - 1.0 * s[j1] * s[j1]) / (s[j2] - s[j1]);
return (Y(j2) - Y(j1)) / (X(j2) - X(j1));
}
int main()
{
#ifdef LOCAL
freopen("C:/input.txt", "r", stdin);
#endif
int n, l;
cin >> n >> l;
++l; //方便计算
for (int i = 1; i <= n; ++i)
scanf("%lld", &s[i]), s[i] += s[i - 1] + 1;
ls[tail++] = 0; //可以通过0转移
for (int i = 1; i <= n; ++i)
{
while (head + 1 < tail && slope(ls[head], ls[head + 1]) <= 2 * (s[i] - l)) //队列有两个元素 队首没第二个优
head++; //淘汰表头
int j = ls[head]; //通过表头转移
f[i] = f[j] + (s[i] - s[j] - l) * (s[i] - s[j] - l);
while (head + 1 < tail && slope(ls[tail - 2], ls[tail - 1]) >= slope(ls[tail - 1], i)) //插入后不满足斜率递增
tail--; //淘汰表尾
ls[tail++] = i; //插入当前节点
}
cout << f[n] << endl;
return 0;
}