HDU-3507 Print Article(斜率优化dp)

24 篇文章 0 订阅

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=3507

题目大意:给你一个集合,里面有n个单词,每个单词都有一个对应的价值ci,你每次可以取出区间[l,r]中一连串的单词,取出这一串单词的花费为(M为已知的),现在要你求出取完所有单词的最小花费是多少。

题目思路:这题是一个斜率优化dp的基础题了,通过题意我们可以很容易就推出如下的递推式:

dp[i]=min(dp[j]+(sum[i]-sum[j])^2+M)(j=1,2,...,i-1)

dp[i]代表取完前 i 个单词所需要的最小花费,sum[i]表示花费ci的前缀和。

要完成这个递推式的时间复杂度是O(n^2),但这题n高达5e5,这个时间复杂度肯定是无法通过的。

这个时候我们就要借助斜率优化来将时间复杂度从O(n^2)降到O(n),具体的原理和实现如下:

当我们取到第 i 个数时,假设对于k<j<i,取 j 时要优于 k,我们就可以得到

dp[j]+(sum[i]-sum[j])^2+M < dp[k]+(sum[i]-sum[k])^2+M(对于这题肯定是花费越少越优的)

那么通过移项合并同类项之后可以得到如下式子

sum[i]>(dp[j] + sum[j]^2 - (dp[k] + sum[k]^2)) / (2*(sum[j]-sum[k]))

我们再令Yj=dp[j] + sum[j]^2,Yk=dp[k] + sum[k]^2,Xj=2*sum[j],Xk=2*sum[k],那么上式就可以化为

sum[i]>(Yj-Yk)/(Xj-Xk),此时方程的右边就已经化成了一个类似于斜率公式的式子了,此时再令f(j,k)=(Yj-Yk)/(Xj-Xk)

而我们前面假设此时取 j 是优于取 k 的,所以当sum[i]>f(j,k)时,取 j 是要优于取 k 的。

如果仍然有k<j<i,并且有f(j,k) > f(i,j) ,那么我们就可以认为 j 这个点对后续的点的更新是无法造成更有的影响的(因为当

f(i,j)<sum[i]时,选择 i 是要优于 j 的,而当f(j,k) > f(i,j)>=sum[i]时,选择 k 也是要优于 j 的),这样我们就可以通过剔除一些对后续更新没有影响的点,来降低时间复杂度;

这样的话,对于我们优化本题就可以做出如下的操作:

1、构造一个单调队列来维护前 i 个数对后续解的影响。

2、对第 i 位求解时,如果队列中存在a[1],a[2],a[3]三个元素,当f(a[2],a[1]) < sum[i]时,就说明选择a2 是要优于a1的,我们就将a1出队,直到满足f(a[i+1],a[i])>=sum[i]时,就可以对dp值进行更新;

3、对于第 i 位的ci入队时,假设队列中从头到尾已经有元素a[1],a[2],a[2]。那么当d要入队的时候,我们维护队列的上凸性质,即如果g[d,a[3]]<g[a[3],a[2]],那么就将a[3]点删除。直到找到g[d,x]>=g[x,y]为止,并将d点加入在该位置中。

AC代码如下:

#include <bits/stdc++.h>
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define fi first
#define se second
#define pb push_back
#define MP make_pair
using namespace std;
typedef long long LL;
typedef pair<int,int>pii;
typedef pair<LL,LL>pll;
typedef double db;
const int MX=5e5+7;

int n,m;
LL dp[MX],sum[MX];
LL q[MX];
LL get_dp(int i,int j){
    return dp[j]+(sum[i]-sum[j])*(sum[i]-sum[j])+m;
}
LL get_up(int j,int k){
    return dp[j]+sum[j]*sum[j]-(dp[k]+sum[k]*sum[k]);
}
LL get_down(int j,int k){
    return 2*(sum[j]-sum[k]);
}
int main(){
    while(~scanf("%d%d",&n,&m)){
        for(int i=1;i<=n;i++){
            int x;scanf("%d",&x);
            sum[i]=sum[i-1]+x;
        }
        memset(dp,0,sizeof(dp));
        int head=0,tail=0;
        q[tail++]=0;
        for(int i=1;i<=n;i++){
            while(head+1<tail&&get_up(q[head+1],q[head])<sum[i]*get_down(q[head+1],q[head])){
                //为了降低精度的影响,我们可以将判断由sum[i]>(dp[j] + sum[j]^2 - (dp[k] + sum[k]^2)) / (2*(sum[j]-sum[k]))转化为sum[i]* (2*(sum[j]-sum[k]))>(dp[j] + sum[j]^2 - (dp[k] + sum[k]^2));
                head++;
            }
            dp[i]=max(dp[i],get_dp(i,q[head]));//找到最优的更新点对答案进行更新;
            while(head+1<tail&&get_up(i,q[tail-1])*get_down(q[tail-1],q[tail-2])<=get_up(q[tail-1],q[tail-2])*get_down(i,q[tail-1])){
                //此处同理。
                tail--;
            }
            q[tail++]=i;
        }
        printf("%lld\n",dp[n]);
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值