HDU 2829 Lawrence【斜率优化dp】

13 篇文章 0 订阅

题意:大概就是给你n(1<=n<=1000)个数,要你将其分成m + 1(0<=m<n)组,要求每组数必须是连续的而且要求得到的价值最小。一组数的价值定义为该组内任意两个数乘积之和,如果某组中仅有一个数,那么该组数的价值为0。如:

将“4 5 1 2”分成一组得到的价值为:4*5 + 4*1 + 4*2 + 5*1 + 5*2 + 1*2 = 49;

将“4 5 1 2”分成“4 5”和“1 2”两组得到的价值为:4*5 + 1*2 = 22;

将“4 5 1 2”分成“4”和“5 1 2”两组得到的价值为:0 + 5*1 + 5*2 + 1*2 = 17。


分析:本题可以用动态规划来解决,但需要斜率优化。

定义dp[i][j]表示将前j个数分成i组所得到的价值,sum[i]表示前i个数之和,w[i]表示前i个数分成一组的价值,则:

dp[i][j] = min{dp[i-1][k] + val(k+1, j)} (i-1<=k<j),val(k+1, j)为将第k+1~j个数分为一组的价值。对该式变形得:

dp[i][j] = min{dp[i-1][k] + w[j] - w[k] - sum[k]*(sum[j] - sum[k])}

          = min{dp[i-1][k] - w[k] + sum[k]*sum[k] - sum[k]*sum[j]} + w[j] (i-1<=k<j).

然后,就可以对该dp进行斜率优化。


#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <cmath>

using namespace std;

const int maxn = 1010;
int n, m;
int a[maxn], sum[maxn], w[maxn];
int dp[maxn][maxn];
int q[maxn], head, tail;

int dy(int x, int j, int i)
{
    return dp[x][i] - w[i] + sum[i] * sum[i] - (dp[x][j] - w[j] + sum[j] * sum[j]); 
}

int dx(int j, int i)
{
    return sum[i] - sum[j];
}

void DP()
{
    for (int i = 1; i <= n; ++i) {
        dp[1][i] = w[i];
    }
    for (int i = 2; i <= m + 1; ++i) {
        head = tail = 0;
        q[tail++] = i - 1;
        for (int j = i; j <= n; ++j) {
            while (tail - head >= 2) {
                int p = q[head], k = q[head+1];
                if (dy(i - 1, p, k) < sum[j] * dx(p, k)) {
                    head++;
                } else {
                    break;
                } 
            }
            dp[i][j] = dp[i-1][q[head]] + w[j] - w[q[head]] - sum[q[head]] * (sum[j] - sum[q[head]]);
            while (tail - head >= 2) {
                int p = q[tail-2], k = q[tail-1];
                if (dy(i - 1, p, k) * dx(k, j) >= dx(p, k) * dy(i - 1, k, j)) {
                    tail--;
                } else {
                    break;
                }
            }
            q[tail++] = j;
        }
    }
    printf("%d\n", dp[m+1][n]); 
}

int main()
{
    while (scanf("%d%d", &n, &m) != EOF) {
        if (n == 0 && m == 0) break;
        sum[0] = 0;
        w[0] = 0;
        for (int i = 1; i <= n; ++i) {
            scanf("%d", &a[i]);
            sum[i] = sum[i-1] + a[i];
            w[i] = w[i-1] + sum[i-1] * a[i]; 
        }
        DP();
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值