DP优化学习笔记

版权声明:_ https://blog.csdn.net/lunch__/article/details/85172712

暑假的时候本来想把这些东西系统学一遍,但是想着NOIP不考就没有学,现在也该补坑了…

1.单调队列优化

待更

2.斜率优化

先从一个例题看起,HDU3507意思是你有一串数,你需要把他们分成很多段连续的子段,每一段的花费是子段内所有数的和的平方,求最小花费。

我们可以很容易列出一个状态转移方程,设 d p i dp_i 为已经给 [ 1 , i ] [1,i] 这个区间分好段的答案,那么 d p i = min j = 1 i 1 d p j + ( k = j + 1 i C k ) 2 dp_i=\min_{j = 1}^{i-1}dp_j+(\sum_{k=j+1}^iC_k)^2 ,这个转移是 O ( n 2 ) O(n^2) 的,不足以通过这题 5 × 1 0 5 5\times10^5 的数据,我们需要优化它。

k < j < i k < j < i S i = j = 1 i C j S_i=\sum_{j=1}^iC_j ,我们来寻找什么时候从 j j 转移到 i i 比从 k k 转移到 i i 更加优秀,我们可以列出一个不等式:
d p j + ( S i S j ) 2 d p k + ( S i S k ) 2 d p j + S i 2 + S j 2 2 S i S j d p k + S i 2 + S k 2 2 S i S k d p j d p k + S j 2 S k 2 2 S i ( S j S k ) d p j d p k + S j 2 S k 2 S j S k 2 S i dp_j+(S_i-S_j)^2\le dp_k + (S_i-S_k)^2 \\ dp_j+S_i^2+S_j^2-2S_iS_j\le dp_k+S_i^2+S_k^2-2S_iS_k\\ dp_j-dp_k+S_j^2-S_k^2\le 2S_i(S_j-S_k)\\ \frac{dp_j-dp_k+S_j^2-S_k^2}{S_j-S_k}\le2S_i

f x = d p x + S x 2 f_x=dp_x+S_x^2 ,那么:
f j f k S j S k 2 S i \frac{f_j-f_k}{S_j-S_k}\le2S_i

我们可以知道当 f j f k S j S k 2 S i \frac{f_j-f_k}{S_j-S_k}\le2S_i 时,决策点 k k i i 更加优秀,又因为这个式子跟直线的斜率式相同,我们考虑通过维护一个斜率单调的决策点集合来实现 O ( 1 ) O(1) 转移,那么我们现在来证明决策点是由一些斜率单调的直线上的点组成的。设 g ( k , j ) = f j f k S j S k g(k,j)=\frac{f_j-f_k}{S_j-S_k} ,对于三个决策点 k < j < p k<j<p ,我们来证明一定不存在 g ( k , j ) g ( j , p ) g(k,j)\ge g(j,p) 的情况,也就是这些直线的斜率是单调下降的。当 g ( k , j ) > g ( j , p ) > 2 S i g(k,j)> g(j,p)> 2S_i 时,决策点 j j p p 优秀, k k j j 优秀,所以 j j 无论如何都不会转移到其他状态,当 g ( k , j ) > 2 S i > g ( j , p ) g(k,j)>2S_i>g(j,p) p , k p,k 都比 j j 优秀,所以 j j 还是不能转移到其他状态,当 2 S i > g ( k , j ) > g ( j , p ) 2S_i>g(k,j)>g(j,p) 时, j j k k 优秀, p p j j 优秀, j j 还是没有任何作用,那么我们在斜率递减时就把没用的决策点删除,最后维护的就是一个凸壳,转移的时候把这些决策点用一个单调队列保存下来,就可以做到 O ( n ) O(n) 了。

Hint:用单调队列优化时必须满足横坐标单调递增,否则就需要动态维护这个凸壳了。

UPD:上面那种方法是从代数角度理解,体现不了斜率优化的精髓,所以再写一种几何相关的方法。

还是这个式子 d p i = d p j + ( S i S j ) 2 dp_i = dp_j+(S_i-S_j)^2 ,化简之后可以得到 d p i + 2 S i S j = S i 2 + S j 2 dp_i + 2S_iS_j=S_i^2+S_j^2 ,这个我们把它看成一次函数的形式,令 y = S i 2 + S j 2 , x = S j , k = 2 S i , b = d p i y=S_i^2+S_j^2,x=S_j,k=2S_i,b=dp_i ,那么所有的决策点我们可以把它放到一个二维平面上去,转移的时候就是给了你一个斜率,要你让这条直线穿过一个决策点满足截距最小。
在这里插入图片描述
如图所示,那条直线就是当前考虑的点,中间的一堆点就是决策点,我们不断向左平移这条直线,碰到的第一个点就是最优的决策点。我们发现只有凸壳上的点才会转移到当前点,那么我们简化一下状态,就变成了这样。

在这里插入图片描述

那么我们只要维护这个下凸壳就好了,由于加入的直线斜率是递增的,那么每个凸壳最前面的一段直线可能会变得不符合条件,然后就可以了。

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;
}

3.决策单调性优化

猜你喜欢

转载自blog.csdn.net/lunch__/article/details/85172712
今日推荐