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

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

题意:

\(n\leq5000\)个 宽度为\(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)) \]
其中\(0\leq 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;
}

猜你喜欢

转载自www.cnblogs.com/zxytxdy/p/12325334.html