昨天搞校选拔赛,耽误了前几天的补题+写题进度,现在疯狂赶…
J Wood Processing
题意:有 个木头,每个木头有宽度和高度,现在你可以随意砍掉他们的高度,使得他们能分成 段(两个木头如果高度相同就是一段),求砍掉的木头最小的总面积
解法:先给木头按高度从小到大排序,设 是前 个木头的总面积, 是前 个木头的总宽度,设 为前 个木头切成 段所切除的最小总面积,不难想到转移方程 ,显然这是个kn^2复杂度,我们可以把它变换一下,把无关紧要的全部看成一坨,令 ,那么 ,相信大家都会用斜率优化dp写这题了吧,如果不会斜率优化,那我简单讲一下。发现这个转移方程其实是一个一次函数,并且随着 变大,斜率 变小,那我们可以用单调队列维护所有的“一次函数”,如下图:
单调队列维护了1,2,3号线,假设当前 变大了,超过了 但是小于第二个交点横坐标,那么1号线无论如何都不会是最优的线了,因此删除队首元素,然后dp就由新队首2号线转移得到,更新完dp后,我们就加入新的4号线,如下图:
我们发现4号线和队尾3号线交点横坐标 小于3号线和2号线交点横坐标 ,那么显然横坐标取任何值,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]);
}