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

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

题意:有 n n 个木头,每个木头有宽度和高度,现在你可以随意砍掉他们的高度,使得他们能分成 k k 段(两个木头如果高度相同就是一段),求砍掉的木头最小的总面积
解法:先给木头按高度从小到大排序,设 S [ i ] S[i] 是前 i i 个木头的总面积, s u m [ i ] sum[i] 是前 i i 个木头的总宽度,设 d [ k ] [ i ] d[k][i] 为前 i i 个木头切成 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]) ,显然这是个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] ,那么 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]) ,相信大家都会用斜率优化dp写这题了吧,如果不会斜率优化,那我简单讲一下。发现这个转移方程其实是一个一次函数,并且随着 j j 变大,斜率 h [ j + 1 ] h[j+1] 变小,那我们可以用单调队列维护所有的“一次函数”,如下图:

在这里插入图片描述

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

在这里插入图片描述

我们发现4号线和队尾3号线交点横坐标 t 1 t1 小于3号线和2号线交点横坐标 t 2 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]);
}

发布了302 篇原创文章 · 获赞 98 · 访问量 7万+

猜你喜欢

转载自blog.csdn.net/ccsu_cat/article/details/99699867