L - Elevator
做题对拍过程中发现一组hack样例,能hackCF很多代码,甚至是ucup前排队伍的代码。
(经过知乎评论区指正,该样例为错误样例,因为 k 保证为偶数,但仍保留该样例,留作参考)
题意
有一个载重为
k
k
k 的电梯,有若干重量为
w
=
1
w=1
w=1 或
2
2
2 的货物需要运送到某一楼层,一次运送的代价为本次运送楼层最高的层数(总重量不能超过电梯载重)。问运送完所有货物最小代价。
货物以
c
i
,
w
i
,
f
i
c_i,w_i,f_i
ci,wi,fi 的形式给出,分别代表货物的数量,重量,需要送到的楼层。
思路
以贪心的思路,肯定是 优先运送楼层更高的货物(因为楼层越高消耗的能量越多),并在此基础上优先运重量为 2 2 2 的货物。 因为两者在耗费能量都是一样的,但凡高楼层剩下 1 1 1 或者 2 2 2 都是要再跑一遍高楼层的。所以根据经典的 1 , 2 1,2 1,2 贪心(先运重量为 2 2 2 的若是还剩下 1 1 1 个空间此时就用重量为 1 1 1 的填补,反之则不行会造成浪费)。
于是思路的基调就定下了,剩下的则是实现问题。
我们可以按楼层排序,将同楼层的货物存在一起(只要楼层和重量相同,那么可以直接将数量相加看做同一种货物)从高楼层开始三指针遍历。
第一个指针
i
i
i 代表当前开始运送该楼层的货物,第二个指针
l
1
l_1
l1 代表当前需要用该楼层的重量为
1
1
1 货物来弥补高楼层的空缺(当高楼层的运完时,可能某一趟电梯没有填满,此时就可以用低楼层的货物来填),第三个指针
l
2
l_2
l2 代表需要用该楼层的重量为
2
2
2 的货物弥补空缺。
总结贪心和实现思路:先运高楼层的重量为 2 2 2 的货物,再运送 1 1 1 的,当某一趟电梯没有填满时,就从高到低选次高楼层的 2 2 2 货物,再是 1 1 1 货物。(楼层不同选楼层高的,楼层相同选重的)。
代码
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 1e5 + 10;
ll n, k;
ll sum[N][2]; // 需要送到 i 层的 重量为 j 的包裹的数量
int f[N];
void solve(){
cin >> n >> k;
int cnt = 0;
for(int i = 1; i <= n; i ++){
int ci, wi, fi;
cin >> ci >> wi >> fi;
sum[fi][wi & 1] += ci;
f[++ cnt] = fi;
}
sort(f + 1, f + 1 + cnt);
int m = unique(f + 1, f + 1 + cnt) - (f + 1);
ll ans = 0;
ll p = k / 2;
for(int i = m, l_1 = m, l_2 = m; i >= 1; i --){
ll fl = f[i];
if(!sum[fl][0] && !sum[fl][1]) continue ;
// 优先运2
ll x = sum[fl][0] / p, r = sum[fl][0] % p; // 需要运多少次才能把本层的2物品运完
ans += fl * (x + (r != 0));
sum[fl][0] = 0; // 本层的2已经运完
if(k & 1){ // k是奇数,每次运2后可以有1个位置来运1
while(l_1 >= 1 && x){ // 多余的位置从上到下用1补上
ll y = min(x, sum[f[l_1]][1]);
x -= y;
sum[f[l_1]][1] -= y;
if(!sum[f[l_1]][1]) l_1 --;
}
}
ll d = k - (r * 2); // 有一次没运满剩下d的空间,
if(d < k){
// 先运本层剩下的1
ll y = min(sum[fl][1], d);
d -= y;
sum[fl][1] -= y;
while(d && (l_1 >= 1 || l_2 >= 1)){ // 优先运楼层高的,楼层一样就先运2再运1
if(d == 1 && l_1 < 1) break;
if(l_2 >= l_1 && d > 1){
ll y = min(d / 2, sum[f[l_2]][0]);
d -= (y * 2);
sum[f[l_2]][0] -= y;
if(!sum[f[l_2]][0]) l_2 --;
}
else{
ll y = min(d, sum[f[l_1]][1]);
d -= y;
sum[f[l_1]][1] -= y;
if(!sum[f[l_1]][1]) l_1 --;
}
}
}
if(sum[fl][1]){ // 最后本层还剩下1,也需要运完
ll _x = sum[fl][1] / k, _r = sum[fl][1] % k;
ans += fl * (_x + (_r != 0));
sum[fl][1] = 0;
if(_r == 0) continue ;
_r = k - _r;
while(_r && (l_1 >= 1 || l_2 >= 1)){ // 优先运楼层高的,楼层一样就先运2再运1
if(_r == 1 && l_1 < 1) break;
if(l_2 >= l_1 && _r > 1){
ll y = min(_r / 2, sum[f[l_2]][0]);
_r -= (y * 2);
sum[f[l_2]][0] -= y;
if(!sum[f[l_2]][0]) l_2 --;
}
else{
ll y = min(_r, sum[f[l_1]][1]);
_r -= y;
sum[f[l_1]][1] -= y;
if(!sum[f[l_1]][1]) l_1 --;
}
}
}
}
for(int i = 1; i <= m; i ++) sum[f[i]][0] = sum[f[i]][1] = 0;
cout << ans << "\n";
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int t;
cin >> t;
while(t --){
solve();
}
return 0;
}
给出一组hack样例:(该样例为错误样例,因为题目保证 k 是偶数)
1
4 5
5 2 4
5 2 3
4 1 2
4 2 1