昨天搞校选拔赛,耽误了前几天的补题+写题进度,现在疯狂赶…
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[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] 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]) 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]);
}