hdoj 3507 Print Article

本文介绍了一种通过斜率优化动态规划(DP)的方法来降低复杂度的技术。具体讲解了如何利用斜率来判断不同状态转移的优劣,并通过一个单调队列实现高效的斜率维护过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  我的第一道斜率优化dp。

  首先,O(n^2)复杂度的dp是很容易想到的。现在我们需要用斜率优化把每次转移的复杂度优化到O(1)。考虑从dp(a)和dp(b)转移到dp(i),若从dp(a)转移要优,则有dp(a)+(sum(i)-sum(a))^2+M<dp(b)+(sum(i)-sum(b))^2+M,移项得到(dp(a)+sum(a)^2-dp(b)-sum(b)^2)/(2*(sum(a)-sum(b)))<sum(i)。设Y(x)=dp(x)+sum(x)^2,X(x)=2*sum(x),代入后得到(Y(a)-Y(b))/(X(a)-X(b))<sum(i),这不就是斜率吗!

  于是可以得到这样的关系:(Y(a)-Y(b))/(X(a)-X(b))<sum(i) <=> 由a转移较优;(Y(a)-Y(b))/(X(a)-X(b))>sum(i) <=> 由b转移较优。假设有从左到右的a,b,c三个点,如果ab的斜率大于bc的斜率,那么dp(i)一定不会由b转移得到。用一个单调队列去维护递增的斜率(就像凸包一样),就可以迅速完成状态转移。

  当状态转移的优劣可以用一个形如“斜率”的式子比较的时候,就可以考虑斜率优化了!另外要注意,能不用除法和浮点数的时候,就不要去用。


#include <iostream>    
#include <stdio.h>    
#include <cmath>    
#include <algorithm>    
#include <string>  
#include <string.h>    
#include <memory.h>    
#include <vector>    
#include <queue>    
#include <stack>
#include <set>

using namespace std;

#define ll long long 

int n,m;

ll c[500010];
ll sum[500010];
ll dp[500010]; 

const ll INF = 1e18;

inline ll getY(int id){
    return sum[id]*sum[id] + dp[id];
}

inline ll getX(int id){
    return 2*sum[id];
}

int que[500010];

int main(){
    while(cin>>n>>m){
        for(int i=1;i<=n;i++){
            scanf("%I64d",&c[i]);
            sum[i] = sum[i-1] + c[i];
        }
        
        int head = 0;
        int tail = 0;
        
        que[tail++] = 0;
        for(int i=1;i<=n;i++){
            while(head+1<tail && (getY(que[head+1])-getY(que[head])) <= sum[i] * (getX(que[head+1])-getX(que[head])) ){
                head++;
            }
            
            int j = que[head];
            dp[i] = (sum[i]-sum[j])*(sum[i]-sum[j])+m+dp[j];
        
            while(head+1<tail && (getY(que[tail-1])-getY(que[tail-2])) * (getX(i)-getX(que[tail-1])) >= 
                    (getY(i)-getY(que[tail-1])) * (getX(que[tail-1])-getX(que[tail-2])) ){
                tail--;
            }
            que[tail++] = i;
        }
        
//        for(int i=1;i<=n;i++){
//            dp[i] = INF;
//            for(int j=0;j<i;j++){
//                dp[i] = min(dp[i],(sum[i]-sum[j])*(sum[i]-sum[j])+m+dp[j]);
//            }
//        }

        cout<<dp[n]<<endl;        
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值