HDU 3045

斜率优化dp

第一次做斜率优化dp,惯例写详细点。。

题意:给你N个数,将它们分成若干组,要求每组至少包含T个数。将每一组的数全部变为该组最小的数。进行合适的分组,使得数字的减小量的总和最小。求这个最小值。


    首先很容易想到将这N个数字从小到大排序。递推式就很容易出来:dp[j] = min(dp[i] + sum[j] - sum[i] - num[i+1]*(j-i))  i<=j-T  其中,dp[i] 表示将前 i 个数分组的最小费用,sum[i]表示前 i 个数的和,num[i]表示第 i 个数。由于题目给出的 N 最大有 4*10^5 直接推,两个for循环超时妥妥的。这里用到了斜率优化,将复杂度从O(n^2) 降到O(n)

    假设 i<j<p ,那么对于 p 从i,j递推来的两个值分别为:dp[i] + sum[p] - sum[i] - num[i+1]*(p-i) 和 dp[j] + sum[p] - sum[j] - num[j+1]*(p-j) 。如果 i 不优于 j ,那么就一定满足:dp[j] + sum[p] - sum[j] - num[j+1]*(p-j) <= dp[i] + sum[p] - sum[i] - num[i+1]*(p-i)  整理后得:dp[j]-sum[j]+num[j+1]*j - (dp[i]-sum[i]+num[i+1]*i) <= (num[j+1]-num[i+1])*p   令yj=dp[j]-sum[j]+num[j+1]*j ,xj=num[j+1] 那么上式就能整理为:(yj-yi)/(xj-xi) <= p 左边就是一个斜率表达式了。方便描述,这里记g[i,j] = (yj-yi)/(xj-xi)。如何利用这个斜率优化呢?

    优化1:假设 i<j<k<p , 如果g[j,k]<=g[i.j] 那么可以忽略点 j 。原因如下:当g[j,k]<=p ,对于 p 来说,j 不优于 k ;当 g[j,k]>p ,那么g[i,j]>=g[j,k]>p,对于 p 来说 i 优于 j 。去掉所有类似 j 的点后,斜率呈现严格递增。

    基于优化1有优化2:假设 i<j<p<q , 如果g[i,j]<=p ,那么对于点 p ,i 不优于 j ;对于点 q ,g[i,j]<=p<q ,i 劣于 j 。假设我们找到了 p 的最优解 j ,那么 j 以前的解一定不是 q 的最优解。

    有了这两个优化,复杂度就成功的降到了O(n)。结合代码细细体会。over

#include<stdio.h>
#include<iostream>
#include<algorithm>
using namespace std;

#define Maxn 400010
int N,T,q[Maxn],head,tail;
long long num[Maxn],dp[Maxn],sum[Maxn];

long long getDp(int i,int j);
long long getY(int i,int j);
long long getX(int i,int j);
int main()
{
    while(scanf("%d%d",&N,&T)!=EOF)
    {
        dp[0]=sum[0]=num[0]=head=tail=0;
        q[tail++]=0;
        for(int i=1;i<=N;i++)
        {
            scanf("%I64d",&num[i]);
            sum[i]=sum[i-1]+num[i];
        }
        sort(num+1,num+1+N);
        for(int i=T;i<=N;i++)
        {
            while(head+1<tail && getY(q[head],q[head+1])<=i*getX(q[head],q[head+1]))
                head++;
            dp[i]=getDp(q[head],i);
            int j=i-T+1;
            if(j<T)
                continue;
            while(head+1<tail && getY(q[tail-1],j)*getX(q[tail-2],q[tail-1])<=getY(q[tail-2],q[tail-1])*getX(q[tail-1],j))
                tail--;
            q[tail++]=j;
        }
        printf("%I64d\n",dp[N]);
    }
    return 0;
}

long long getDp(int i,int j)
{
    return dp[i]+sum[j]-sum[i]-num[i+1]*(j-i);
}
long long getY(int i,int j)
{
    return dp[j]-sum[j]+num[j+1]*j-(dp[i]-sum[i]+num[i+1]*i);
}
long long getX(int i,int j)
{
    return num[j+1]-num[i+1];
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值