HDU 3507 Print Article(斜率优化DP)

题意

有一篇文章,每个字有一个权值,文章中每行的花费为这行上面所有数字之和的平方再加上一个常数m,要求整篇文章的最小花费。

思路

首先它是个dp题目,我们先把转移方程写出来
d p [ i ] = d p [ j ] + ( s u m [ i ] − s u m [ j ] ) 2 + M ( 对 于 所 有 0 < j < i ) dp[i] = dp[j] + (sum[i] - sum[j])^2 + M (对于所有 0 < j < i ) dp[i]=dp[j]+(sum[i]sum[j])2+M(0<j<i)

没有优化的做法
没有优化,直接做,可以知道时间复杂度是O(n2)的,而 n <= 105 会超时。

代码如下:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 5e5 + 10;
const ll INF = 0x3f3f3f3f3f3f3f3f;
ll a[maxn], sum[maxn], dp[maxn];
int main()
{

    ll n, m;
    scanf("%lld %lld", &n, &m);
    for(int i = 1; i <= n; i++){
        scanf("%lld", &a[i]);
        sum[i] = sum[i - 1] + a[i];
    }
    memset(dp, INF, sizeof(dp));
    dp[1] = sum[1] * sum[1] + m;
    for(int i = 2; i <= n; i++){
        for(int j = 1; j < i; j++){
            if(dp[i] > dp[j] + pow(sum[i] - sum [j], 2) + m)
                dp[i] = dp[j] + pow(sum[i] - sum [j], 2) + m;
        }
    }
    printf("%lld\n", dp[n]);
    return 0;
}

使用斜率优化
d p [ i ] = d p [ j ] + ( s u m [ i ] − s u m [ j ] ) 2 + M ( 对 于 所 有 0 < j < i ) dp[i] = dp[j] + (sum[i] - sum[j])^2 + M (对于所有 0 < j < i ) dp[i]=dp[j]+(sum[i]sum[j])2+M(0<j<i)

假设 k < j < i并且从 dp[j] 转移到 dp[i] 比从 dp[k] 转移到 dp[i] 更优

那么就有
d p [ j ] + ( s u m [ i ] − s u m [ j ] ) 2 + M < d p [ k ] + ( s u m [ i ] − s u m [ k ] ) 2 + M dp[j] + (sum[i] - sum[j])^2 + M < dp[k] + (sum[i] - sum[k])^2 + M dp[j]+(sum[i]sum[j])2+M<dp[k]+(sum[i]sum[k])2+M
进行化简有
( d p [ j ] + s u m [ j ] 2 ) − ( d p [ k ] + s u m [ k ] 2 ) 2 ∗ ( s u m [ j ] − s u m [ k ] ) < s u m [ i ] \frac{(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)<sum[i]
这个时候,左边是斜率,右边是一个值
y [ j ] − y [ k ] x [ j ] − x [ k ] < a \frac{y[j] - y[k]}{x[j] - x[k]} < a x[j]x[k]y[j]y[k]<a

符合该条件时,假设成立。

也就等价于一个平面上求两点的斜率,当两点斜率小于等于 ai 时要转移。

那么对于斜率大于ai的两点,我们把它略去,显然这是在维护一个斜率的下凸包。

例如下图
在这里插入图片描述

所以,我们一开始先求一下sum数组,然后维护斜率的下凸包,最后一边跑dp一边求y数组即可。

维护斜率的下凸包用一个队列que[]。

时间复杂度为O(n)
代码如下:

#include <cstdio>
#include <cstdlib>
using namespace std;
typedef long long ll;
const int maxn = 5e5 + 10;
ll a[maxn];
ll sum[maxn];
ll dp[maxn];
ll que[maxn];
ll get_up(int u, int v)///Y
{
    return (dp[u] + sum[u] * sum[u]) - (dp[v] + sum[v] * sum[v]);///视情况修改
}
ll get_down(int u, int v)///x
{
    return 2 * (sum[u] - sum[v]);///视情况修改
}
int main()
{
    ll n, m;
    while(scanf("%lld %lld", &n, &m) != EOF)
    {
        for(int i = 1; i <= n; i++)
        {
            scanf("%lld", &a[i]);
            sum[i] = sum[i - 1] + a[i];
        }
        int L = 1, R = 1;
        for(int i = 1; i <= n; i++)
        {
            //如果斜率小于右侧ai,那么向前转移
            while(L < R && get_up(que[L + 1], que[L]) <= sum[i] * get_down(que[L + 1], que[L]))
                L++;
            //求出dp的值
            dp[i] = dp[que[L]] + (sum[i] - sum[que[L]]) *(sum[i] - sum[que[L]]) + m;
            //维护下凸性
            while(L < R && get_up(i, que[R]) * get_down(que[R], que[R - 1]) <= get_up(que[R], que[R - 1]) * get_down(i, que[R]))
                R--;
            que[++R] = 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、付费专栏及课程。

余额充值