HDU 3507 Print Article(DP斜率优化)

题目链接
题意

将n个数任意划分成若干区间,每个区间贡献为,区间内权值和的平方+m,求最小的贡献和是多少。

思路

斜率DP入门题。
d p [ 前 i 个 最 小 贡 献 ] dp[前i个最小贡献] dp[i] p r e [ 前 i 个 元 素 前 缀 和 ] pre[前i个元素前缀和] pre[i] 快速求区间和用
最简单的DP思路, d p [ i ] = ∑ j = 1 i − 1 m i n ( d p [ j ] + ( p r e [ i ] − p r e [ j ] ) 2 + m ) dp[i] = \sum_{j=1}^{i-1}min(dp[j]+(pre[i]-pre[j])^2+m) dp[i]=j=1i1min(dp[j]+(pre[i]pre[j])2+m) O ( n 2 ) O(n^2) O(n2) 显然超时。

k 1 > k 2 k1>k2 k1>k2,在 k 1 k1 k1 处转移比 k 2 k2 k2 处转移更优

d p [ k 1 ] + ( p r e [ i ] − p r e [ k 1 ] ) 2 + m > d p [ k 2 ] + ( p r e [ i ] − p r e [ k 2 ] ) 2 + m dp[k1]+(pre[i]-pre[k1])^2+m>dp[k2]+(pre[i]-pre[k2])^2+m dp[k1]+(pre[i]pre[k1])2+m>dp[k2]+(pre[i]pre[k2])2+m

化简移项 d p [ k 1 ] + p r e [ k 1 ] 2 − d p [ k 2 ] − p r e [ k 2 ] 2 p r e [ k 1 ] − p r e [ k 2 ] &lt; 2 p r e [ i ] \frac{dp[k1]+pre[k1]^2-dp[k2]-pre[k2]^2}{pre[k1]-pre[k2]}&lt;2pre[i] pre[k1]pre[k2]dp[k1]+pre[k1]2dp[k2]pre[k2]2<2pre[i]

写不动了下面简略写。具体学习链接
维护一个下凸的斜率,由于 p r e [ i ] pre[i] pre[i] 单调增,斜率只要留一个小于当前更新到的 2 ∗ p r e [ i ] 2*pre[i] 2pre[i] 即可

代码
#include <bits/stdc++.h>
using namespace std;

#define ll long long


ll read(ll &_a){return scanf("%lld",&_a);}
ll rd(){ll _tmp; scanf("%lld",&_tmp); return _tmp;}

ll pre[500005], dp[500005], que[500005];

ll getd(ll i, ll j, ll ii, ll jj)
{
    return (dp[i]+pre[i]*pre[i]-dp[j]-pre[j]*pre[j])*(pre[ii]-pre[jj]) >= (dp[ii]+pre[ii]*pre[ii]-dp[jj]-pre[jj]*pre[jj])*(pre[i]-pre[j]);
}

ll getd(ll i, ll j, ll num)
{
    return (dp[j]+pre[j]*pre[j]-dp[i]-pre[i]*pre[i]) <= num*(pre[j]-pre[i]);
}

int main()
{
    ll n, m;
    while(~scanf("%lld%lld",&n,&m))
    {
        ll head = 0, tail = 0;
        que[0] = 0;
        // 初始插个斜率为0的进去,使转移方程可以从头开始
        for(ll i = 1; i <= n; ++i)
        {
            read(pre[i]), pre[i] += pre[i-1];
            ll k1 = que[head], k2 = que[head+1];
            while(head < tail && getd(k1,k2,pre[i]*2ll) ) ++head, k1 = que[head], k2 = que[head+1];
            dp[i] = dp[k1]+(pre[i]-pre[k1])*(pre[i]-pre[k1])+m;
            while(head < tail && getd(que[tail],que[tail-1],i,que[tail]) ) --tail;
            que[++tail] = i;
        }
        printf("%lld\n",dp[n]);
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值