2019牛客暑期多校第十场 J.Wood Processing

2019牛客暑期多校第十场 J.Wood Processing

题意:

(nleq5000)个 宽度为(w_i),高为(h_i) 的 木块,要求分成(k)组,对于每组内的所有木块,高度都变为组内最低木块的高度,宽度保持不变,求变化的最小面积。

思路:

先按照高度从大到小排个序。

其实就相当于将这些序列cut成k份,每份有一个代价,要求总代价最小。

每一份中的代价取决于什么呢?

  • 这一份木板中最矮的那一个的高度。
  • (其他木板的高度与最矮的高度之差)*其他木板的宽度。

知道这些之后,就可以开始(dp)了。

这时候可以设(f(i,j))表示考虑到第(i)块木板,切成了(j)份的最小代价是多少。

但是这样不太好,因为每一份中木板的高度不尽相同,所以势必让转移方程变复杂。

所以换一个思路,设(f(i,j))表示考虑到第(i)块木板,切了(j)份之后最大的面积是多少。

[f(i,j)=max(f(k,j-1)+(sW(i)-sW(k)*h(i)) ]

其中(0leq k< i)(sW)表示宽度的前缀和。此时答案为(tot-f(n,k)),其中(tot)表示木板总面积。

由于需要枚举(i,j,k),时间复杂度(O(n^3)),无法通过。

展开上式

[f(i,j)=sW(i)h(i)+max{f(k,j-1)-sW(k)*h(i)} ]

去掉(max:)

[f(i,j)=sW(i)h(i)+f(k,j-1)-sW(k)*h(i) ]

[f(k,j-1)=sW(k)*h(i)+[f(i,j)-sW(i)h(i)] ]

这样的话就变成斜率(dp)的形式了。

我们发现转移方程成了以(sW(k))为自变量,(f(k,j-1))为因变量,(h(i))为斜率,(f(i,j)-sW(i)h(i))为截距的直线。

当截距最大时,(f(i,j))取到最大。

所以用单调队列维护一个上凸壳。

#include<bits/stdc++.h>
using namespace std;
const int maxn = 5000+10;
typedef long long ll;
struct Node{
    ll w, h;
}a[maxn];
bool cmp(Node a, Node b){
    return a.h > b.h;
}

int n, k;
int q[maxn];
ll sum[maxn];
ll f[maxn][maxn];
ll area;

long double slope(int x,int y,int p){
    return (long double)1.0*(f[x][p-1]-f[y][p-1])/(sum[x]-sum[y]);
}

int main()
{
    scanf("%d%d", &n, &k);
    for(int i = 1; i <= n; i++)
        scanf("%lld%lld", &a[i].w, &a[i].h);
    sort(a+1, a+1+n, cmp);
    for(int i = 1; i <= n; i++)
    {
        sum[i] = sum[i-1]+a[i].w;
        area += a[i].h*a[i].w;
    }


    int hh, tt;
    //队列中数据单调递减
    for(int j = 1; j <= k; j++)
    {
        hh = 1, tt = 1;
        for(int i = 1; i <= n; i++)
        {
            while(hh < tt && slope(q[hh],q[hh+1],j) >= a[i].h) hh++;
            int t = q[hh];
            f[i][j] = f[t][j-1]+(sum[i]-sum[t])*a[i].h;
            while(hh < tt && slope(q[tt],q[tt-1],j) <= slope(q[tt],i,j)) tt--;
            q[++tt] = i;
        }
    }
    cout << (area-f[n][k]) << endl;
    return 0;
}

原文地址:https://www.cnblogs.com/zxytxdy/p/12325334.html