【训练题62:dp + 斜率优化】Print Article | HDU3507

题意

  • Print Article | HDU3507
    有一个长度为 n n n 的序列 C C C
    把它分成一些子区间,总的花费为:
    ∑ 每 个 子 区 间 k ( ( ∑ i 在 子 区 间 k C i ) 2 + M ) \sum_{每个子区间 k} \Big( \Big( \sum_{i在子区间k}C_i \Big)^2+M \Big) k((ikCi)2+M)
    求总花费的最小值
  • 1 ≤ n ≤ 5 × 1 0 5 1\le n\le 5\times10^5 1n5×105
    1 ≤ C i ≤ 1 0 3 1\le C_i\le 10^3 1Ci103

思路

  • 我们直接进行一个 D N A DNA DNA 的动,设 d p [ i ] dp[i] dp[i] 表示选完了 1 ∼ i 1\sim i 1i 的位置,最小的总花费
    记录一个前缀和数组 p r e [ x ] = ∑ i = 1 x C i pre[x]=\sum_{i=1}^x C_i pre[x]=i=1xCi
    转移也很简单:
    d p [ i ] = min ⁡ j = 0 i − 1 { d p [ j ] + ( p r e [ i ] − p r e [ j ] ) 2 } + M dp[i]=\min_{j=0}^{i-1}\{ dp[j]+(pre[i]-pre[j])^2 \} + M dp[i]=j=0mini1{dp[j]+(pre[i]pre[j])2}+M
    但是这个复杂度很明显为 O ( n 2 ) \color{red}{O(n^2)} O(n2) ,我们怎么去优化呢?
    根据决策单调性的相关知识,我们可以通过某种方式将其优化!
  • 0 ≤ k 2 < k 1 < i 0\le k_2<k_1<i 0k2<k1<i ,假设我们从 k 1 k_1 k1 那边转移到 i i i 更优,那么我们有:
    d p [ k 2 ] + ( p r e [ i ] − p r e [ k 2 ] ) 2 < d p [ k 1 ] + ( p r e [ i ] − p r e [ k 1 ] ) 2 dp[k_2]+(pre[i]-pre[k_2])^2 < dp[k_1]+(pre[i]-pre[k_1])^2 dp[k2]+(pre[i]pre[k2])2<dp[k1]+(pre[i]pre[k1])2
  • 化简得到:
    ( d p [ k 2 ] − d p [ k 1 ] + p r e [ k 2 ] 2 − p r e [ k 1 ] 2 ) / ( p r e [ k 2 ] − p r e [ k 1 ] ) > 2 × p r e [ i ] (dp[k_2]-dp[k_1]+pre[k_2]^2-pre[k_1]^2)/(pre[k_2]-pre[k_1])>2\times pre[i] (dp[k2]dp[k1]+pre[k2]2pre[k1]2)/(pre[k2]pre[k1])>2×pre[i]
    我们令 Y ( x ) = d p [ x ] + p r e [ x ] 2 Y(x)=dp[x]+pre[x]^2 Y(x)=dp[x]+pre[x]2
    X ( x ) = p r e [ x ] X(x)=pre[x] X(x)=pre[x]
    那么式子就变成了:
    Y ( k 2 ) − Y ( k 1 ) X ( k 2 ) − X ( k 1 ) > 2 × p r e [ i ] \frac{Y(k_2)-Y(k_1)}{X(k_2)-X(k_1)}>2\times pre[i] X(k2)X(k1)Y(k2)Y(k1)>2×pre[i]
  • 而右边我们放的是一个常数,假设为 K ( i ) K(i) K(i) ,那式子可以想成:其中两个点之间的斜率大于 K ( i ) K(i) K(i)
    因为我们一开始定义 k 1 k_1 k1 更优的话,要求有这个式子
    我们可以记录,从这个点 k 1 k_1 k1 出发,到之后的某个 k 2 k_2 k2 ,斜率最小是多少? 如果斜率最小都 > K ( i ) >K(i) >K(i) 了,自然选这个点是最最优的。
    我们可以使用求凸包的方法,快速去维护。
    注意到,随着 i i i 递增, p r e [ i ] pre[i] pre[i] 也是单递增的,也就是我们的 K ( i ) K(i) K(i) 也是单递增 的,我们就可以用类似单调队列 的写法,达到 O ( N ) \color{green}{O(N)} O(N) 的优秀复杂度

更套路的做法

  • 主要是上面的转移,希望写得更快一点 (???) ,有新的办法去做
    考虑式子:
    d p [ i ] = min ⁡ j = 0 i − 1 { d p [ j ] + ( p r e [ i ] − p r e [ j ] ) 2 } + M dp[i]=\min_{j=0}^{i-1}\{ dp[j]+(pre[i]-pre[j])^2 \} + M dp[i]=j=0mini1{dp[j]+(pre[i]pre[j])2}+M
  • 我们省略 min ⁡ \min min 函数,拆开来变成:
    d p [ i ] = d p [ j ] + p r e [ i ] 2 + p r e [ j ] 2 − 2 p r e [ i ] p r e [ j ] + M dp[i]=dp[j]+pre[i]^2+pre[j]^2-2pre[i]pre[j]+M dp[i]=dp[j]+pre[i]2+pre[j]22pre[i]pre[j]+M
    由于考虑斜率,我们希望写成 y = k x + b y=kx+b y=kx+b 的形式:
    d p [ j ] + p r e [ j ] 2 = 2 p r e [ i ] p r e [ j ] + ( d p [ i ] − p r e [ i ] 2 − M ) dp[j]+pre[j]^2=2pre[i]pre[j]+(dp[i]-pre[i]^2-M) dp[j]+pre[j]2=2pre[i]pre[j]+(dp[i]pre[i]2M)
    这里,只与 j j j 有关的函数我们放在 y y y 的位置
    i , j i,j i,j 都有关的函数我们放在 k x kx kx 的位置,并令 k k k 表示只与 i i i 有关的内容,令 x x x 表示只与 j j j 有关的内容
    只与 i i i 有关的函数我们放在 b b b 的位置
    然后我们发现,其实就是之前的 Y ( x ) = d p [ x ] + p r e [ x ] 2 Y(x)=dp[x]+pre[x]^2 Y(x)=dp[x]+pre[x]2 X ( x ) = p r e [ x ] X(x)=pre[x] X(x)=pre[x] K ( x ) = 2 p r e [ x ] K(x)=2pre[x] K(x)=2pre[x]
  • 接下来,我们需要求 d p [ i ] dp[i] dp[i] 最小,就是希望我们 b b b 最小;由于我们的 x , k x,k x,k 都是单递增的,所以我们构造 下凸包,就可以达到我们的目的
    请添加图片描述
  • 我这里给出一个稍微封装的代码,就非常利于理解了吧

代码

  • 时间复杂度: O ( N ) O(N) O(N)
#include <bits/stdc++.h>
#define IOS ios::sync_with_stdio(false);cin.tie(NULL);cout.tie(NULL);
using namespace std;
typedef long long ll;
void show(){std::cerr << endl;}template<typename T,typename... Args>void show(T x,Args... args){std::cerr << "[ " << x <<  " ] , ";show(args...);}

const int MAX = 5e5+50;
const int MOD = 1e9+7;
const int INF = 0x3f3f3f3f;
const ll LINF = 0x3f3f3f3f3f3f3f3f;
const double EPS = 1e-5;

ll pre[MAX];
ll dp[MAX];
int M;
inline ll X(int x){return pre[x];}				   // 修改这里
inline ll Y(int x){return dp[x] + pre[x] * pre[x];}	// 修改这里
inline ll K(int x){return 2LL * pre[x];}		   // 修改这里
inline ll DX(int a,int b){return X(a) - X(b);}
inline ll DY(int a,int b){return Y(a) - Y(b);}
inline void F(int i,int j){						  // 修改这里
    dp[i] = dp[j] + (pre[i] - pre[j]) * (pre[i] - pre[j]) + M;
}
int Q[MAX],head,rear;
int main()
{
    int n;
    while(n = read()){
        M = read();
        for(int i = 1;i <= n;++i){
            int t = read();
            pre[i] = pre[i-1] + t;
            dp[i] = 0;
        }
        head = rear = 1;
        Q[head] = 0;dp[0] = 0;
        for(int i = 1;i <= n;++i){		// 注意符号方向 这是一个下凸包
            while(head < rear && DY(Q[head],Q[head+1]) >= K(i) * DX(Q[head],Q[head+1]))++head;
            int j = Q[head];
            F(i,j);
            while(head < rear && DY(Q[rear-1],Q[rear])* DX(Q[rear],i) >= DY(Q[rear],i) * DX(Q[rear-1],Q[rear]))--rear;
            Q[++rear] = i;
        }
        cout << dp[n] << endl;
    }

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值