很多棵树,砍掉一些树使得最高的树的个数是总数的两倍,问砍树的最小花费。
wa了很多发,写篇博客纪念一下(并不值得纪念)。
每棵树都有成为最高的树的潜力,只要你把比它高的都砍掉。
那么把树按高度排序,首先计算把比它高的树都砍掉的花费以及砍了多少树,这部分用两个前缀和O(1)求出。
如果砍完比它高的树就已经满足条件了,那么更新答案,如果还不行,就还要砍掉一些比它矮的,这时候贪心的砍花费小的,由于花费最大只有200,整个桶(计数数组)暴力维护一下,如果花费特别大,就需要离散化+线段树了(事实上我都写完了线段树了正准备离散化,回去一看数据范围立马窒息,由于题解里也是线段树高度怀疑出题人把c和p搞反了)。
wa了很多发,一开始是没注意到原来相同高度但是砍掉代价不同的会分开输入,然后就是各种写挫,愣是不忍心删代码于是写篇博客存着。
ac代码:
#include
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 5;
int n;
struct trees {
ll h, c, p;
} ts[maxn];
bool cmp(trees &a, trees &b) {
return a.h < b.h;
}
//价值前缀和,个数前缀和
ll sum[maxn], sum2[maxn];
ll cost[205];
void solve() {
memset(cost, 0, sizeof(cost));
ll cut = 0, val = 0;
const ll all = sum2[n];
ll ans = INT64_MAX;
for (int i = 1; i <= n; ++i) {
int st = i;
ll sump = ts[i].p;
while (ts[i + 1].h == ts[i].h) {
sump += ts[++i].p;
}
cut = sum2[n] - sum2[i];
val = sum[n] - sum[i];
if (sump > (all - cut) / 2) {
ans = min(ans, val);
} else {
ll tot = 0;
for (int j = 1; j <= 200; ++j) {
if (!cost[j]) {
continue;
}
if (sump <= (all - (cut + cost[j])) / 2) {
tot += cost[j] * j;
cut += cost[j];
} else {
tot += (all - 2 * sump - cut + 1) * j;
break;
}
}
ans = min(ans, val + tot);
}
for (int j = st; j <= i; ++j) {
cost[ts[j].c] += ts[j].p;
}
}
printf("%lld\n", ans);
}
int main() {
while (~scanf("%d", &n)) {
for (int i = 1; i <= n; ++i) {
scanf("%lld%lld%lld", &ts[i].h, &ts[i].c, &ts[i].p);
}
ts[n + 1] = {-1, -1, -1};
sort(ts + 1, ts + n + 1, cmp);
for (int i = 1; i <= n; ++i) {
sum[i] = sum[i - 1] + ts[i].c * ts[i].p;
sum2[i] = sum2[i - 1] + ts[i].p;
}
solve();
}
return 0;
}
/*
5
1 9 2
2 7 6
3 1 1
4 3 1
5 100 1
4
1 9 6
1 4 7
1 6 1
1 8 8
6
2 4 2
2 3 2
3 2 2
3 1 2
4 5 2
4 6 2
*/