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

作为斜率优化dp的入门题,可以说的非常经典了。斜率dp是一类数形结合的题目,数的方面让你写出程序,而形的方面则让你更清楚的理解正确性。

首先,这题的状态转移方程是很好写的:

dp[i]=min(dp[i],dp[j]+(sum[j]−sum[i])2+M)

我们优化的最终目的就是要在o(1)的时间内可以求出最优的dp[j]。

下面的做法就是斜率dp的一般套路:

首先,在斜率dp中,一定要同时讨论两个单调性(自变量的单调性和因变量的单调性,对应于这题就是,i(下标是一个个递增的),dp[i]),不同时讨论这两个变量的单调性都是在耍流氓。

先假设j<k,同时dp[k]优于dp[j]。(同时满足单调性)

然后就可以列出方程:dp[k]+(sum[k]−sum[i])2+M < dp[j]+(sum[j]−sum[i])2+M .

然后就是化简(化简的时候不要忘记j,k的大小,因为会影响不等号方向)

得:

dp[j]+sum[j]^2-(dp[k]+sum(k)^2) }/( sum[j]-sum[k] ) < 2*sum[i];

也就是说,只要满足上面的不等式,k就会优于j,那么我们就可以直接忽略j。

只要维护上面的性质,我们就可以在o(1)时间找到最优的值。

下面考虑,把第i项加入到队列中的情况:

令 solve( j,k ) = { dp[j]+sum[j]^2-(dp[k]+sum(k)^2) }/( sum[j]-sum[k] );
对于任意的 j,k,p ( j<k<p ),有solve( j,k ),solve( k,p );

如果 solve(j,k) > solve(k,p) ,那么有如下两种情况:
 

if( solve( k,p )<2*sum[i] ) p优于k,则k是无用的决策
     if( solve( k,p )>2*sum[i] ) 能推出solve( j,k )>2*sum[i],也就是说k不比j优,同样的k是无用的决策,可以去掉
     so,只要满足这个大的IF条件,那么k就是无用的决策

上面的情况画出图就是说,只要构成一个上凸点,那么上凸的点必然不是最优的点,可以去除。

其实这也是一个结论:最优值只会在下凸点取得,所以思路和维护一个凸包的点很类似。

总结一下就是:分两步,1 先维护队首元素的最优性质,得出dp[i]后,考虑将第i个点放入队列中,同时利用类似凸包的方法维护。

下面介绍一下一位大佬做出的数形解释:

https://blog.csdn.net/qq_34542903/article/details/54563477

代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int inf=0x3f3f3f3f;
const int maxn=5e5+7;
const int mod=1e9+7;
ll dp[maxn],sum[maxn];
int q[maxn];
int n,m;
int solve(int j,int k) //k优于j
{
    if(sum[j]==sum[k])
    {
        if(dp[j]>dp[k])
        {
            return -1;
        }
        else
        {
            return inf;
        }
    }
    return (dp[j]+sum[j]*sum[j]-(dp[k]+sum[k]*sum[k]))/(sum[j]-sum[k]);
}
int main()
{
    #ifndef ONLINE_JUDGE
        freopen("in.txt","r",stdin);
        freopen("out.txt","w",stdout);
    #endif
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        sum[0]=0;
        for(int i=1;i<=n;i++)
        {
            scanf("%lld",&sum[i]);
            sum[i]+=sum[i-1];
        }
        int head,tail;
        head=tail=0;
        dp[0]=0;
        for(int i=1;i<=n;i++)
        {
            while(head<tail&&solve(q[head],q[head+1])<2*sum[i])
            {
                head++;
            }
            dp[i]=dp[q[head]]+(sum[i]-sum[q[head]])*(sum[i]-sum[q[head]])+m;
            while(head<tail&&solve(q[tail-1],q[tail])>solve(q[tail],i))
            {
                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、付费专栏及课程。

余额充值