题解 | #来硬的#

来硬的

https://www.nowcoder.com/practice/08dbc77ec79942c1914b002f488b87ee

HIGH91 来硬的

题目描述

共有 枚煤炭和 单位的铁矿石。第 枚煤炭可以融化 单位的铁矿石,燃烧时间为 秒。

你拥有一项魔法,最多可以对一枚煤炭施放,将其升级。若第 枚煤炭被升级,它将可以融化 单位的矿石,而燃烧时间缩短为 秒。

熔炉同一时刻只能燃烧一枚燃料。请求出将所有 单位铁矿石全部烧炼完毕,所需要的最短总时间。

解题思路

这是一个在满足特定“价值”(融化量)要求下,最小化“成本”(燃烧时间)的问题,是典型的背包问题变种。然而,由于单个煤炭的融化量 可能非常大,直接以融化量为维度的DP数组会导致内存溢出。

正确的思路是基于数据范围进行分类讨论和剪枝。任何最优解,要么是仅使用单个煤炭完成任务,要么是使用多个“小”煤炭的组合完成。

1. 策略分类

我们将煤炭分为两类:

  • 大煤炭: 能够单独完成任务的煤炭,即
  • 小煤炭: 单独无法完成任务的煤炭,即

一个重要的观察是:任何包含“大煤炭”的最优方案,必然只包含那一个“大煤炭”。因为加入任何其他煤炭只会增加时间,而融化量已经足够。

因此,所有可能的解可以被划分为:

  1. 单煤炭方案:
    • 使用一个“大煤炭”(不升级)。
    • 使用一个煤炭(无论大小),将其升级后能够满足
  2. 多煤炭方案:
    • 只使用“小煤炭”的组合来完成任务。在这个组合中,我们仍然可以选择对其中一枚“小煤炭”施放魔法。

2. 算法流程

  1. 预处理:

    • 初始化一个全局的最小时间 min_time 为无穷大。
    • 创建一个列表 small_coals 用于存放所有“小煤炭”。
    • 遍历所有 枚煤炭 (x_i, y_i)
      • 检查单煤炭方案:
        • 如果不升级,若 ,用 更新 min_time
        • 如果升级,若 ,用 更新 min_time
      • 如果 ,则将其加入 small_coals 列表。
  2. 对“小煤炭”进行动态规划:

    • 现在的问题是在 small_coals 集合中,融化至少 矿石的最小时间,其中最多一个可升级。这是一个可以用DP解决的子问题。
    • 关于Python I/O: 本题的数据规模( 可达 )意味着输入量可能很大。在Python中,标准的 input() 函数在处理大量输入时性能较低,可能导致超时。因此,Python代码实现将采用 sys.stdin.readline() 以优化读取速度。
    • 状态定义:
      • $dp_0[j]$: 在 small_coals尚未使用魔法,恰好融化 单位矿石所需的最短时间。
      • $dp_1[j]$: 在 small_coals已经使用过一次魔法,恰好融化 单位矿石所需的最短时间。
    • DP容量: 最终融化量可能超过 。一个安全的上界是 加上“小煤炭”中最大的升级融化量。因为小煤炭 ,所以 ,DP数组容量设为 足够。
    • 状态转移: 遍历 small_coals,用标准的0/1背包(逆序遍历容量)和分层DP的思想更新 dp0dp1 数组。
    • DP求解: DP完成后,遍历所有 的容量,用 的值更新全局 min_time
  3. 最终答案: 全局 min_time 即为所求。

代码实现

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

const int INF = 1e9 + 7;

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);

    int n;
    long long m;
    cin >> n >> m;

    vector<pair<long long, int>> small_coals;
    long long max_small_x = 0;
    long long min_time = -1;

    for (int i = 0; i < n; ++i) {
        long long x;
        int y;
        cin >> x >> y;

        // 检查单煤炭方案
        if (x >= m) {
            if (min_time == -1 || y < min_time) {
                min_time = y;
            }
        }
        if (2 * x >= m) {
            if (min_time == -1 || y / 2 < min_time) {
                min_time = y / 2;
            }
        }

        // 收集小煤炭
        if (x < m) {
            small_coals.push_back({x, y});
            max_small_x = max(max_small_x, x);
        }
    }

    if (!small_coals.empty()) {
        long long capacity = m + 2 * max_small_x;
        vector<long long> dp0(capacity + 1, -1);
        vector<long long> dp1(capacity + 1, -1);
        dp0[0] = 0;
        dp1[0] = 0;

        for (const auto& coal : small_coals) {
            long long x = coal.first;
            int y = coal.second;

            for (long long j = capacity; j >= 0; --j) {
                // 更新 dp1 (已用魔法)
                if (j >= x && dp1[j - x] != -1) {
                    long long new_time = dp1[j - x] + y;
                    if (dp1[j] == -1 || new_time < dp1[j]) dp1[j] = new_time;
                }
                if (j >= 2 * x && dp0[j - 2 * x] != -1) {
                    long long new_time = dp0[j - 2 * x] + y / 2;
                    if (dp1[j] == -1 || new_time < dp1[j]) dp1[j] = new_time;
                }
                
                // 更新 dp0 (未用魔法)
                if (j >= x && dp0[j - x] != -1) {
                    long long new_time = dp0[j - x] + y;
                    if (dp0[j] == -1 || new_time < dp0[j]) dp0[j] = new_time;
                }
            }
        }

        for (long long j = m; j <= capacity; ++j) {
            if (dp0[j] != -1) {
                if (min_time == -1 || dp0[j] < min_time) min_time = dp0[j];
            }
            if (dp1[j] != -1) {
                if (min_time == -1 || dp1[j] < min_time) min_time = dp1[j];
            }
        }
    }

    cout << min_time << endl;

    return 0;
}
import java.util.Scanner;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class Main {
    static class Coal {
        long x;
        int y;
        Coal(long x, int y) {
            this.x = x;
            this.y = y;
        }
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        long m = sc.nextLong();

        List<Coal> smallCoals = new ArrayList<>();
        long maxSmallX = 0;
        long minTime = -1;

        for (int i = 0; i < n; i++) {
            long x = sc.nextLong();
            int y = sc.nextInt();

            if (x >= m) {
                if (minTime == -1 || y < minTime) minTime = y;
            }
            if (2 * x >= m) {
                if (minTime == -1 || y / 2 < minTime) minTime = y / 2;
            }

            if (x < m) {
                smallCoals.add(new Coal(x, y));
                maxSmallX = Math.max(maxSmallX, x);
            }
        }

        if (!smallCoals.isEmpty()) {
            int capacity = (int)(m + 2 * maxSmallX);
            long[] dp0 = new long[capacity + 1];
            long[] dp1 = new long[capacity + 1];
            Arrays.fill(dp0, -1);
            Arrays.fill(dp1, -1);
            dp0[0] = 0;
            dp1[0] = 0;

            for (Coal coal : smallCoals) {
                long x = coal.x;
                int y = coal.y;

                for (int j = capacity; j >= 0; j--) {
                    // Update dp1
                    if (j >= x && dp1[j - (int)x] != -1) {
                        long newTime = dp1[j - (int)x] + y;
                        if (dp1[j] == -1 || newTime < dp1[j]) dp1[j] = newTime;
                    }
                    if (j >= 2 * x && dp0[j - (int)(2 * x)] != -1) {
                        long newTime = dp0[j - (int)(2 * x)] + y / 2;
                        if (dp1[j] == -1 || newTime < dp1[j]) dp1[j] = newTime;
                    }
                    
                    // Update dp0
                    if (j >= x && dp0[j - (int)x] != -1) {
                        long newTime = dp0[j - (int)x] + y;
                        if (dp0[j] == -1 || newTime < dp0[j]) dp0[j] = newTime;
                    }
                }
            }
            
            for (int j = (int)m; j <= capacity; j++) {
                if (dp0[j] != -1) {
                    if (minTime == -1 || dp0[j] < minTime) minTime = dp0[j];
                }
                if (dp1[j] != -1) {
                    if (minTime == -1 || dp1[j] < minTime) minTime = dp1[j];
                }
            }
        }

        System.out.println(minTime);
    }
}
import sys

def main():
    n, m = map(int, sys.stdin.readline().split())
    
    small_coals = []
    max_small_x = 0
    min_time = float('inf')

    for _ in range(n):
        x, y = map(int, sys.stdin.readline().split())
        
        # Check single coal solutions
        if x >= m:
            min_time = min(min_time, y)
        if 2 * x >= m:
            min_time = min(min_time, y // 2)
            
        # Collect small coals
        if x < m:
            small_coals.append((x, y))
            max_small_x = max(max_small_x, x)

    if small_coals:
        capacity = int(m + 2 * max_small_x)
        dp0 = [float('inf')] * (capacity + 1)
        dp1 = [float('inf')] * (capacity + 1)
        dp0[0] = 0
        dp1[0] = 0

        for x, y in small_coals:
            for j in range(capacity, -1, -1):
                # Update dp1
                if j >= x and dp1[j - x] != float('inf'):
                    dp1[j] = min(dp1[j], dp1[j - x] + y)
                if j >= 2 * x and dp0[j - 2 * x] != float('inf'):
                    dp1[j] = min(dp1[j], dp0[j - 2 * x] + y // 2)
                
                # Update dp0
                if j >= x and dp0[j - x] != float('inf'):
                    dp0[j] = min(dp0[j], dp0[j - x] + y)

        for j in range(int(m), capacity + 1):
            min_time = min(min_time, dp0[j], dp1[j])

    print(min_time)

if __name__ == "__main__":
    main()

算法及复杂度

  • 算法: 动态规划 (0/1背包变种) + 分类讨论
  • 时间复杂度: ,其中 是总煤炭数, 是“小煤炭”的数量。 用于预处理和分类, 是DP部分的时间复杂度,因为DP的容量上界与 成正比。
  • 空间复杂度: ,需要 的空间存储输入数据,以及 的空间存储DP数组。
全部评论

相关推荐

04-24 13:51
已编辑
西安电子科技大学 Java
👋个人背景:211计算机混子,代码能力一般,春招急头白脸参加央国企最后拿下这两个offer👏offer1:中广核工程公司驻陆丰仪控调试,待遇19+4,离家1800km💯offer2:张家口卷烟厂待遇未知,应该有13个(猜测),离家500km牛油们帮忙选一下,家里人不是很喜欢卷烟厂这个offer,但是蜀黍烟草局下岸了
鸿雁于飞:先说offer1:中广核工程公司驻陆丰仪控调试(待遇19+4) 中广核这艘央企大船还是很稳的,集团综合效益稳居央企前列。但你得搞清楚,这个19+4的"19"是总包,不是到手数——招聘宣传待遇里把所有能算的都算进去了,饭卡福利积分啥的全包含,有牛油分享实际到手大概打七折。试用期到手可能就四五千的水平,转正后基本工资4800左右,其余靠绩效、年终、大修费撑着。不过核电的工作环境有点"牢笼感"——核电站位置偏僻,远离繁华都市。工程公司是承包商性质,干活比业主公司累,而且大概率要经常出差,有的岗位年出差天数100天以上。最大问题是你这1800km的距离过于离谱,核电员工工作强度最小的时候一周也就回一次家,离得远回家成本高,夫妻感情和亲子关系都是现实考验。说白了:高薪是拿青春和生活换的。 再来看offer2:张家口卷烟厂(待遇约13个) 张家口卷烟厂是河北中烟下属三家卷烟厂之一,河北中烟主打的"荷花"系列连续多年位居全国高端卷烟品牌销量前列。烟草系统薪资由基本工资+绩效+年终奖构成,综合年薪普遍显著高于当地平均水平,六险二金齐全,福利拉满。有人问"13个是不是太平平无奇了"——关键张家口是四线城市,生活成本低,这13万的购买力相当于深圳的二十多万。离家500km,开车半天到家,周末回趟家完全可行,幸福感直接上两个档次。中广核的牛油说了句大实话: "哪个核电站好?永远是离家近的那个最好。" 选烟厂同理。 但是,卷烟厂的坑你得清楚: 首先卷烟厂和烟草局不一样,卷烟厂是生产操作类岗位,很多要三班倒。报考条件明确写了要能"胜任夜班工作和长时间站立工作"。一线操作工每天盯着流水线卷烟,工作内容高度重复,有入职的人描述为"食之无味弃之可惜"。有牛油直言"卷烟厂和商业性质的烟草公司不一样,前者很坑很累"。其次你家里人不是不喜欢,而是担心你这211计算机科班出身,进了烟厂干操作工,技能会快速退化,未来如果行业改革,技术壁垒不高,转行比较困难。等你干两年再跳出来,技术栈全忘干净了,回头再去敲代码,发现连应届生都卷不过。 老牛油的灵魂三问: 1. 你是更怕穷,还是更怕想家? 如果特别恋家的人跑1800km之外,第一年哭鼻子的概率高达80%。陆丰那地方偏僻单调,核电基地又远又闷,闲下来除了打游戏没啥娱乐,社交圈也窄。找个对象都费劲——牛油亲测核电站"狼多肉少"。 2. 你的代码能力有多"一般"? 如果真的一般,仪控调试和你专业匹配度不算高,这活儿主要是工程改造设计、现场实施管理、在建机组设计审查等,偏工程向而非纯软开。干两年后跳回互联网赛道,竞争力不一定有明显提升。反倒是烟厂不需要你写代码,进去就是稳定躺平。 3. 烟草局下岸这事儿会不会让你耿耿于怀? 如果烟草局是你第一志愿,烟厂只是plan B,那得想清楚:进去了可能每天看着天花板想"如果当初去了烟草局该多好",这种内耗比钱少还折磨人。如果你能接受"反正都是烟草系统,先进去再说"的心态,那倒无所谓。 一句话总结: 如果年轻想拼想闯做技术积累,中广核虽然累和远,但简历上央企核电的金字招牌确实有含金量,加上到手收入在这两个选项里确实更高,考虑到你个人经济情况和家庭状况,假如家里不需要你常回去照顾,家里有兄弟姐妹帮手分担,那先去核电待三四年,积累经验再跳槽也不失为一步棋。 如果想安稳过日子离家近当"人上人",烟厂低线生活成本加持,加上稳定的编制和福利体系,在张家***得滋润,幸福感吊打陆丰。尤其家里人是那种离不开你的,有烟厂的稳定且离家近,比任何高薪都实在。
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务