暑假的时候本来想把这些东西系统学一遍,但是想着NOIP不考就没有学,现在也该补坑了…
1.单调队列优化
待更
2.斜率优化
先从一个例题看起,HDU3507意思是你有一串数,你需要把他们分成很多段连续的子段,每一段的花费是子段内所有数的和的平方,求最小花费。
我们可以很容易列出一个状态转移方程,设 为已经给 这个区间分好段的答案,那么 ,这个转移是 的,不足以通过这题 的数据,我们需要优化它。
设
,
,我们来寻找什么时候从
转移到
比从
转移到
更加优秀,我们可以列出一个不等式:
设
,那么:
我们可以知道当 时,决策点 比 更加优秀,又因为这个式子跟直线的斜率式相同,我们考虑通过维护一个斜率单调的决策点集合来实现 转移,那么我们现在来证明决策点是由一些斜率单调的直线上的点组成的。设 ,对于三个决策点 ,我们来证明一定不存在 的情况,也就是这些直线的斜率是单调下降的。当 时,决策点 比 优秀, 比 优秀,所以 无论如何都不会转移到其他状态,当 时 都比 优秀,所以 还是不能转移到其他状态,当 时, 比 优秀, 比 优秀, 还是没有任何作用,那么我们在斜率递减时就把没用的决策点删除,最后维护的就是一个凸壳,转移的时候把这些决策点用一个单调队列保存下来,就可以做到 了。
Hint:用单调队列优化时必须满足横坐标单调递增,否则就需要动态维护这个凸壳了。
UPD:上面那种方法是从代数角度理解,体现不了斜率优化的精髓,所以再写一种几何相关的方法。
还是这个式子
,化简之后可以得到
,这个我们把它看成一次函数的形式,令
,那么所有的决策点我们可以把它放到一个二维平面上去,转移的时候就是给了你一个斜率,要你让这条直线穿过一个决策点满足截距最小。
如图所示,那条直线就是当前考虑的点,中间的一堆点就是决策点,我们不断向左平移这条直线,碰到的第一个点就是最优的决策点。我们发现只有凸壳上的点才会转移到当前点,那么我们简化一下状态,就变成了这样。
那么我们只要维护这个下凸壳就好了,由于加入的直线斜率是递增的,那么每个凸壳最前面的一段直线可能会变得不符合条件,然后就可以了。
Codes
#include <bits/stdc++.h>
#define x first
#define y second
#define pb push_back
#define mp make_pair
#define inf (0x3f3f3f3f)
using namespace std;
typedef long long ll;
typedef pair<int, int> PII;
template<class T>inline T read(T &_) {
T __ = getchar(), ___ = 1; _ = 0;
for (; !isdigit(__); __ = getchar()) if (__ == '-') ___ = -1;
for (; isdigit(__); __ = getchar()) _ = (_ << 3) + (_ << 1) + (__ ^ 48);
return _ *= ___;
}
template<class T>inline bool chkmax(T &_, T __) { return _ < __ ? _ = __, 1 : 0; }
template<class T>inline bool chkmin(T &_, T __) { return _ > __ ? _ = __, 1 : 0; }
inline void proStatus() {
ifstream t("/proc/self/status");
cerr << string(istreambuf_iterator<char>(t), istreambuf_iterator<char>());
}
const int N = 5e5 + 10;
ll dp[N], S[N], n, m;
inline ll pf(ll x) { return x * x; }
inline ll f(int x) { return dp[x] + pf(S[x]); }
inline ll dy(int x, int y) { return f(y) - f(x); }
inline ll dx(int x, int y) { return S[y] - S[x]; }
int main() {
#ifdef ylsakioi
freopen("3507.in", "r", stdin);
freopen("3507.out", "w", stdout);
#endif
while (~scanf("%lld%lld", &n, &m)) {
static int q[N], l, r;
for (int i = 1; i <= n; ++ i)
read(S[i]), S[i] += S[i - 1];
q[l = r = 1] = 0;
for (int i = 1; i <= n; q[++ r] = i ++) {
while (l < r && dy(q[l], q[l + 1]) < dx(q[l], q[l + 1]) * (S[i] << 1)) ++ l;
dp[i] = dp[q[l]] + pf(S[i] - S[q[l]]) + m;
while (l < r && dy(q[r], i) * dx(q[r - 1], q[r]) <= dy(q[r - 1], q[r]) * dx(q[r], i)) -- r;
}
printf("%lld\n", dp[n]);
}
return 0;
}