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;
}