2023 ICPC 南京题解(L)

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
  • 9
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值