2019牛客暑期多校训练营(第十场)

昨天搞校选拔赛,耽误了前几天的补题+写题进度,现在疯狂赶…
J Wood Processing

题意:有 n n n个木头,每个木头有宽度和高度,现在你可以随意砍掉他们的高度,使得他们能分成 k k k段(两个木头如果高度相同就是一段),求砍掉的木头最小的总面积
解法:先给木头按高度从小到大排序,设 S [ i ] S[i] S[i]是前 i i i个木头的总面积, s u m [ i ] sum[i] sum[i]是前 i i i个木头的总宽度,设 d [ k ] [ i ] d[k][i] d[k][i]为前 i i i个木头切成 k k k段所切除的最小总面积,不难想到转移方程 d [ k ] [ i ] = m i n ( d [ k − 1 ] [ j ] + S [ i ] − S [ j ] − ( s u m [ i ] − s u m [ j ] ) ∗ h [ j + 1 ] ) d[k][i]=min(d[k-1][j]+S[i]-S[j]-(sum[i]-sum[j])*h[j+1]) d[k][i]=min(d[k1][j]+S[i]S[j](sum[i]sum[j])h[j+1]),显然这是个kn^2复杂度,我们可以把它变换一下,把无关紧要的全部看成一坨,令 C [ j ] = d [ k − 1 ] [ j ] + S [ i ] − S [ j ] + s u m [ j ] ∗ h [ j + 1 ] C[j] =d[k-1][j]+S[i]-S[j]+sum[j]*h[j+1] C[j]=d[k1][j]+S[i]S[j]+sum[j]h[j+1],那么 d [ k ] [ i ] = m i n ( C [ j ] − s u m [ i ] ∗ h [ j + 1 ] ) d[k][i]=min(C[j]-sum[i]*h[j+1]) d[k][i]=min(C[j]sum[i]h[j+1]),相信大家都会用斜率优化dp写这题了吧,如果不会斜率优化,那我简单讲一下。发现这个转移方程其实是一个一次函数,并且随着 j j j变大,斜率 h [ j + 1 ] h[j+1] h[j+1]变小,那我们可以用单调队列维护所有的“一次函数”,如下图:

在这里插入图片描述

单调队列维护了1,2,3号线,假设当前 s u m [ i ] sum[i] sum[i]变大了,超过了 t 0 t0 t0但是小于第二个交点横坐标,那么1号线无论如何都不会是最优的线了,因此删除队首元素,然后dp就由新队首2号线转移得到,更新完dp后,我们就加入新的4号线,如下图:

在这里插入图片描述

我们发现4号线和队尾3号线交点横坐标 t 1 t1 t1小于3号线和2号线交点横坐标 t 2 t2 t2,那么显然横坐标取任何值,3号线都不是最优的选择,因此,删除队尾元素3号线即可
#include<bits/stdc++.h>
#define ll long long
#define db double
using namespace std;
const int maxn = 5000 + 10, N = 1e7 + 10;
struct node {
    ll w;
    int h;
    bool operator<(const node& t) const {
        return h < t.h;
    }
} a[maxn];
ll H[N], sum[maxn], S[maxn], inf = 1e18;
ll d[2005][maxn];
int q[maxn];
ll gao(int j, int i, int k) {
    return d[k][j] + S[i] - S[j] - (sum[i] - sum[j]) * a[j + 1].h; //计算dp值
}
db gao2(int i, int j, int k) { //求交点横坐标
    ll tmp1 = d[k][i] - S[i] + sum[i] * a[i + 1].h;
    ll tmp2 = d[k][j] - S[j] + sum[j] * a[j + 1].h;
    ll tmp = tmp1 - tmp2;
    ll tmp3 = a[i + 1].h - a[j + 1].h;
    return tmp / tmp3;
}
int main() {
    int n, k, w, h, t;
    scanf("%d%d", &n, &k);
    for (int i = 1; i <= n; i++) {
        scanf("%d%d", &w, &h);
        H[h] += w;
    }
    n = 0;
    for (int i = 1; i <= 10000000; i++)
        if (H[i])
            a[++n] = node{H[i], i};
    sort(a + 1, a + 1 + n);
    if (n <= k)
        return puts("0"), 0;

    for (int i = 1; i <= n; i++) {
        sum[i] = sum[i - 1] + a[i].w;
        S[i] = S[i - 1] + a[i].w * a[i].h;
    }
    for (int i = 1; i <= n; i++) {
        d[1][i] = S[i] - S[1];
        if (i > 1)
            d[1][i] -= a[1].h * (sum[i] - sum[1]);
    }
    for (int K = 2; K <= k; K++) {
        h = t = 1;
        q[t++] = K - 1;
        for (int i = K; i <= n; i++) {
            while (h + 1 < t && gao(q[h], i, K - 1) >= gao(q[h + 1], i, K - 1))
                h++;
            d[K][i] = gao(q[h], i, K - 1);
            if (i != n)
            while (h + 1 < t && gao2(q[t - 2], q[t - 1], K - 1) >= gao2(q[t - 1], i, K - 1))
                t--;
            q[t++] = i;
        }
    }
    printf("%lld\n", d[k][n]);
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

长沙橘子猫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值