首页 > 试题广场 >

小红闯关

[编程题]小红闯关
  • 热度指数:2163 时间限制:C/C++ 1秒,其他语言2秒 空间限制:C/C++ 256M,其他语言512M
  • 算法知识视频讲解
\,\,\,\,\,\,\,\,\,\,小红在玩一个游戏,这个游戏有 n 个关卡,通过第 i 个关卡需要消耗 a_i 个单位时间,小红必须按从前往后的顺序通过每一个关卡。
\,\,\,\,\,\,\,\,\,\,每通过 k 个关卡,小红会获得一个跳关道具,跳关道具可以在任意一个关卡使用,使用跳关道具后可以不消耗时间直接通过关卡。
\,\,\,\,\,\,\,\,\,\,小红想知道她通过这 n 个关卡,最少需要多少时间。

输入描述:
\,\,\,\,\,\,\,\,\,\,第一行输入两个整数 n, k\left(1 \leq n, k \leq 10^5\right) 代表关卡数量和获得跳关道具的条件。
\,\,\,\,\,\,\,\,\,\,第二行输入 n 个整数 a_1, a_2, \dots, a_n\left(1 \leq a_i \leq 10^5\right) 代表通过每个关卡需要消耗的时间。


输出描述:
\,\,\,\,\,\,\,\,\,\,在一行上输出一个整数,表示小红通过这 n 个关卡所需的最少时间。
示例1

输入

3 2
1 3 2

输出

4

说明

\,\,\,\,\,\,\,\,\,\,小红通过第二个关卡后获得跳关道具,此时消耗 1+3 单位时间;在第三个关卡使用跳关道具,不再消耗时间。
示例2

输入

6 2
1 1 4 5 1 4

输出

7

说明

\,\,\,\,\,\,\,\,\,\,小红通过第二个关卡后获得第一个跳关道具;
\,\,\,\,\,\,\,\,\,\,在第四个关卡使用第一个跳关道具后得到第二个跳关道具;
\,\,\,\,\,\,\,\,\,\,在第六个关卡使用第二个跳关道具。
示例3

输入

5 1
2 4 5 1 3

输出

2

说明

\,\,\,\,\,\,\,\,\,\,通过第一关后,后面的关卡都可以使用跳关道具。跳关也算一次成功的闯关。

贪心 + 最大堆

由题可知,每闯过k个关卡,就能获得一个跳关道具。

因此,下标为k的倍数的关卡都能获得一个跳关道具。

  • [k, n-1]范围内,我们可以对其中的任何一个关卡使用跳关道具。
  • [2k, n-1]范围内,我们可以对其中的任何一个关卡使用跳关道具。
  • ...
  • [jk, n-1]范围内,我们可以对其中的任何一个关卡使用跳关道具。

我们发现,这些范围有些部分是重叠的。对于重叠的部分,我们优先使用范围较小的跳关道具。

[jk, n-1]排除里面的最大值,然后[j-1k, n-1]就能够在剩余的范围内排除第二大的值。如果先使用[j-1k, n-1]排除了最大值,如果恰好第二大值在[j-1k, jk)这个范围内,[jk,n-1]的道具就白白浪费了,它只能排除[jk,n-1]中的最大值,该值在[j-1k,n-1]中可能第三大甚至第四大。

因此,为了充分利用跳关道具,我们需要先使用范围较小的跳关道具。

因此,我们从右往左遍历,当下标是k的倍数时,说明遇到了跳关道具,此时跳关时间加上之前遍历的元素的最大值。

然后继续遍历,遇到跳关道具,跳关时间就加上剩余元素的最大值。

最大值可以使用最大堆来进行记录。

代码实现如下:

import sys
import heapq

line = sys.stdin.readline().strip()
a = line.split()

n = int(a[0])
k = int(a[1])

line = sys.stdin.readline().strip()

nums = [int(num) for num in line.split()]

maxHeap = []
i = n - 1
maxJumpTime = 0
for i in range(n-1, -1, -1):     
    heapq.heappush(maxHeap, -nums[i])
    if i != 0 and i % k == 0:
        maxJumpTime += -heapq.heappop(maxHeap)
print(sum(nums) - maxJumpTime)
算法的时间复杂度是O(N)。需要有一个最大堆,因此空间复杂度O(N)
编辑于 2025-03-30 19:29:03 回复(0)
import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        int k = scanner.nextInt();

        int[] times = new int[n];
        for (int i = 0; i < n; i++) {
            times[i] = scanner.nextInt();
        }

        // 计算能获得的跳关道具数量
        int props = n / k;

        // 如果没有道具,直接返回所有关卡时间总和
        if (props == 0) {
            long sum = 0;
            for (int time : times) {
                sum += time;
            }
            System.out.println(sum);
            return;
        }

        // 使用小顶堆来保存我们要跳过的最大时间关卡
        PriorityQueue<Integer> minHeap = new PriorityQueue<>();

        long totalSum = 0;

        for (int i = 0; i < n; i++) {
            totalSum += times[i];

            // 记录当前已获得的道具数量 (到第i+1关为止)
            int currentProps = i / k;

            // 如果当前已获得的道具数量多于堆的大小,说明刚获得了新道具
            if (currentProps > minHeap.size()) {
                minHeap.add(times[i]);
            }
            // 如果已经获得了道具,且当前关卡时间比堆中最小的要大,则替换
            else if (currentProps > 0 && times[i] > minHeap.peek()) {
                minHeap.poll();
                minHeap.add(times[i]);
            }
        }

        // 计算需要减去的总时间(被跳过的关卡)
        long skipSum = 0;
        while (!minHeap.isEmpty()) {
            skipSum += minHeap.poll();
        }

        System.out.println(totalSum - skipSum);
    }
}

发表于 2025-09-04 21:32:45 回复(0)
java这个有问题啊,用最大堆+贪心只能过11个 
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        String line = scanner.nextLine().trim();
        String[] a = line.split(" ");
       
        int n = Integer.parseInt(a[0]);
        int k = Integer.parseInt(a[1]);
       
        line = scanner.nextLine().trim();
        String[] numStrings = line.split(" ");
        int[] nums = new int[n];
        for (int i = 0; i < n; i++) {
            nums[i] = Integer.parseInt(numStrings[i]);
        }
       
        PriorityQueue<Integer> maxHeap = new PriorityQueue<>((x, y) -> y - x);
        int maxJumpTime = 0;
        Integer sum = 0;
        for (int i = n - 1; i >= 0; i--) {
            sum += nums[i];
            maxHeap.add(nums[i]);
            if (i != 0 && i % k == 0) {
                maxJumpTime += maxHeap.poll();
            }
        }
        System.out.println(sum - maxJumpTime);
    }

发表于 2025-08-16 21:31:09 回复(1)
桶思想+并查集

为啥想桶? 对于每一关, 可能拥有的最大票数是固定的, 为(idx//k), 从整数除法提示我们可以选择把它们看成一个个桶.
但这些桶可能会合并. 比如说, 我使用了第i个桶的一张票, 那第i个桶还能用票吗? 答案是可以的, 它还能用第i-1个桶的票. 所以, 最好用完第i个桶的票后, 就把第i个桶和第i-1个桶合并.

合并两个集合的最常用方法就是并查集. 合并操作用来合并桶, 查询操作用来查询指定桶现在有哪个桶的票.
ans = sum(arr)
m = (n - 1) // k

if m == 0:
    print(ans)
else:
    sort_arr = sorted(range(n), key=lambda i: -arr[i])
    
    buckets = list(range(m+2))

    def find(x):
        if buckets[x] == x:
            return x
        buckets[x] = find(buckets[x])
        return buckets[x]

    def union(a, b):
        buckets[a] = find(buckets[b])

    use_cnt = 0
    for idx in sort_arr:
        bucket = idx // k

        if bucket < 1:
            continue
        tickt = find(bucket)
        if tickt >= 1:
            use_cnt += 1
            ans -= arr[idx]
            union(tickt, tickt-1)
            if use_cnt == m:
                break
    print(ans)


发表于 2025-08-30 21:16:13 回复(0)
#动态规划
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int main() {
    int n, k;
    cin >> n >> k;

    vector<int> a(n);
    for (int i = 0; i < n; ++i) {
        cin >> a[i];
    }

    // dp[i][j] 表示通过前i个关卡,剩余j个道具时的最小时间
    const int INF = 1e18;
    vector<vector<int>> dp(n + 1, vector<int>(n + 1, INF));
    dp[0][0] = 0; // 初始状态:通过0个关卡,剩余0个道具,时间为0

    for (int i = 0; i < n; ++i) {
        for (int j = 0; j <= n; ++j) {
            if (dp[i][j] == INF) continue; // 无效状态

            // 情况1:使用一个道具跳过当前关卡i+1
            if (j > 0) {
                int new_j = j - 1;
                // 检查使用道具后是否会获得新道具
                int new_acquired = (i + 1) % k == 0 ? 1 : 0;
                new_j += new_acquired;
                if (new_j <= n && dp[i + 1][new_j] > dp[i][j]) {
                    dp[i + 1][new_j] = dp[i][j];
                }
            }

            // 情况2:不使用道具,通过当前关卡i+1
            int new_time = dp[i][j] + a[i];
            // 检查通过后是否会获得新道具
            int new_acquired = (i + 1) % k == 0 ? 1 : 0;
            int new_j = j + new_acquired;
            if (new_j <= n && dp[i + 1][new_j] > new_time) {
                dp[i + 1][new_j] = new_time;
            }
        }
    }

    // 找出通过所有n个关卡后的最小时间
    int min_time = INF;
    for (int j = 0; j <= n; ++j) {
        if (dp[n][j] < min_time) {
            min_time = dp[n][j];
        }
    }

    cout << min_time << endl;
    return 0;
}

include
<iostream>
#include <vector>
#include <algorithm>
using namespace std;

int main() {
    int n, k;
    cin >> n >> k;

    vector<int> a(n);
    for (int i = 0; i < n; ++i) {
        cin >> a[i];
    }

    // dp[i][j] 表示通过前i个关卡,剩余j个道具时的最小时间
    const int INF = 1e18;
    vector<vector<int>> dp(n + 1, vector<int>(n + 1, INF));
    dp[0][0] = 0; // 初始状态:通过0个关卡,剩余0个道具,时间为0

    for (int i = 0; i < n; ++i) {
        for (int j = 0; j <= n; ++j) {
            if (dp[i][j] == INF) continue; // 无效状态

            // 情况1:使用一个道具跳过当前关卡i+1
            if (j > 0) {
                int new_j = j - 1;
                // 检查使用道具后是否会获得新道具
                int new_acquired = (i + 1) % k == 0 ? 1 : 0;
                new_j += new_acquired;
                if (new_j <= n && dp[i + 1][new_j] > dp[i][j]) {
                    dp[i + 1][new_j] = dp[i][j];
                }
            }

            // 情况2:不使用道具,通过当前关卡i+1
            int new_time = dp[i][j] + a[i];
            // 检查通过后是否会获得新道具
            int new_acquired = (i + 1) % k == 0 ? 1 : 0;
            int new_j = j + new_acquired;
            if (new_j <= n && dp[i + 1][new_j] > new_time) {
                dp[i + 1][new_j] = new_time;
            }
        }
    }

    // 找出通过所有n个关卡后的最小时间
    int min_time = INF;
    for (int j = 0; j <= n; ++j) {
        if (dp[n][j] < min_time) {
            min_time = dp[n][j];
        }
    }

    cout << min_time << endl;
    return 0;
}
发表于 2025-07-13 14:01:38 回复(0)
//最大堆+从后往前遍历
#include<climits>
#include<iostream>
#include<vector>
#include<algorithm>
#include<iomanip>
#include<queue>
#include<set>
#include<map>
#include<string>
#include<unordered_map>
#include<stack>
#include<sstream>
#include<regex>
using namespace std;


struct ListNode {
    int val;
    struct ListNode* next;
    ListNode(int x) : val(x), next(nullptr) {}
};

int main() {
    int n, k;
    cin >> n >> k;
    vector<int> a(n);
    long long int sum = 0;
    for (int i = 0; i < n; i++) {
        cin >> a[i];
        sum = sum + a[i];
    }
    int m ;
    if (n % k == 0)
    {
        m = n / k - 1;
    }
    else
    {
        m = n / k;
    }
    priority_queue<int>q;
    long long int tt = 0;
    for (int i = n - 1;i >= k;i--)
    {
        if (i%k==0)
        {
            q.push(a[i]);
            tt = tt + q.top();
            q.pop();
        }
        else
        {
            q.push(a[i]);
        }
    }
    cout << sum - tt;
    return 0;
}
发表于 2025-06-02 18:43:58 回复(0)
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
using PII = pair<int, int>;
int main() {
    LL n,k;
    cin>>n>>k;
    vector<LL>a(n);
    LL res=0;
    priority_queue<LL,vector<LL>,less<>>pq;
    for(int i=0;i<n;i++){
        cin>>a[i];
        res+=a[i];
    }
    for(int i=n-1;i>=0;i--){
        pq.push(a[i]);
        if(i!=0 && i%k ==0){
            int mx = pq.top();pq.pop();
            res -= mx;
        }
    }
    cout<<res;
}
// 64 位输出请用 printf("%lld")

发表于 2025-04-09 14:46:30 回复(0)
import java.util.Arrays;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
import java.util.PriorityQueue;
import java.util.Scanner;

// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int k = sc.nextInt();
        int[] a = new int[n];
        for(int i = 0;i < n;i ++ ) a[i] = sc.nextInt();
        PriorityQueue<Integer> pq = new PriorityQueue<>(Comparator.reverseOrder());
        long sum = 0;
        for(int i = n - 1;i >= k;i -- ) {
            pq.add(a[i]);
            sum += a[i];
            if(i % k == 0) {
                sum -= pq.poll();
            }
        }
        for(int i = k - 1;i >= 0;i -- ) sum += a[i];
        System.out.println(sum);
    }
}

发表于 2025-04-01 12:02:01 回复(0)
#include <algorithm>
#include <cstdlib>
#include <iostream>
#include <vector>
#include <queue>
using namespace std;

int cmp(const void*a,const void*b){
    return *(int*)a-*(int *)b;
}

int main() {
    int a, b;
    cin >> a >> b;
    vector<int> arr;
    int tmp = a;
    while (tmp--) { // 注意 while 处理多个 case
        int yl;
        scanf("%d", &yl);
        arr.push_back(yl);
    }
    if(b==1){
        printf("%d",arr[0]);
        return 0;
    }
    long long sum = 0;
    for (int i = 0; i < b; ++i) {
        sum += arr[i];
    }
    int left = a - a % b;
    int right = a;
    priority_queue<int> queue1;
    for(int i=left;i<right;++i){
        queue1.push(arr[i]);
    }
    if(!queue1.empty())
        queue1.pop();
    right = left;
    left = right -b;
    while(left>0){
        for(int i=left;i<right;++i){
            queue1.push(arr[i]);
        }
        queue1.pop();
        right = left;
        left = right -b;
    }
    while(!queue1.empty()){
        sum += queue1.top();
        queue1.pop();
    }
    printf("%lld", sum);
}

发表于 2025-03-28 22:48:00 回复(0)
import sys

for line in sys.stdin:
    a = line.split()
    n, k = int(a[0]), int(a[1])
    num = list(map(int, input().split()))
    jump = n // k  # 有多少次跳的机会
    chance = [1] * (jump + 1)

    # 为每个元素添加索引信息,形成 (index, value) 元组
    num_with_index = [(i, v) for i, v in enumerate(num)]
    # 按照值从大到小排序
    maxnum = sorted(num_with_index, key=lambda x: x[1], reverse=True)

    jian = 0
    for i, x in maxnum:
        j = i // k  # 看在第几次能 jump
        for p in range(j, 0, -1):
            if chance[p] == 1:
                chance[p] = 0
                jump -= 1
                jian += x
                break
        if jump == 0:
            break      print(sum([x[1] for x in maxnum]) - jian)
有点超时了,15/20个用例通过,比一楼的答案通过更多
发表于 2025-03-23 17:21:32 回复(0)