【备战秋招】每日一题:2023.3.15-阿里OD机试(第三题)-k次操作最小化极差

文章描述了一个关于通过‘平衡之门’的数学挑战,其中涉及对一组数字进行‘乘2’或‘除以2’的操作,目标是最小化操作后数组的最大值与最小值之差。通过贪心策略,即按顺序选择数字进行操作,可以找到最小极差的解决方案。文章提供了不同编程语言的代码示例来演示如何实现这一算法,其时间复杂度为O(n^2)。
摘要由CSDN通过智能技术生成

在线评测链接:P1084

题目内容

在一个遥远的王国里,有一座高耸入云的宝塔,据说里面藏有神秘的宝藏。但是,进入宝塔的道路异常困难,需要经过各种险阻,其中一个重要的关卡是“平衡之门”。

平衡之门是一条走廊,两边是无数个数码显示屏幕,每个屏幕上都显示着一个整数。在走廊中央有一个控制台,上面有两个按钮,一个是“乘2”按钮,一个是“除以 2 2 2”按钮。每次按下其中一个按钮,控制台上的数字就会相应地变化。走廊中的每个数字最多只能被操作一次。

王国的宝藏猎人们想要通过平衡之门,以最小的代价进入宝塔。他们发现,只需要选择恰好 k k k 个数字,使得每个数字经过不超过一次的操作后,可以得到所有的数字,就可以顺利通过平衡之门。现在他们需要你的帮助,计算出最小的极差,即经过操作后最大值和最小值之差的最小值。

输入描述

第一行输入两个正整数 n n n k k k ,代表数组长度以及选择的元素数量。

第二行输入 n n n个元素,代表给定的数组。

1 ≤ k ≤ n ≤ 1 0 5 1 \leq k \leq n \leq 10^5 1kn105

1 ≤ a i ≤ 1 0 9 1 \leq a_i \leq 10^9 1ai109

输出描述

k k k 次操作后,数组极差的最小值。

样例

输入输出示例仅供调试,后台判题数据一般不包含示例

输入

4 2
1 4 3 5

输出

2
样例

输入

6 1
9 4 3 4 2 5

输出

3

思路

贪心

1.观察到每个数最多修改一次,那么显然最优的修改方案一定是前 i i i小都乘以 2 2 2,前 k − i k-i ki大都除以 2 2 2

2.那么排序,枚举 i i i然后模拟这个过程,去求最大值最小值,复杂度达到 O ( n 2 ) O(n^2) O(n2) 。但好在我们只关心极差,即最大值和最小值,所以不需要去模拟操作每一个数。而是考虑一次操作后结果会分成三段,分别去求这三段的最值就好了。如下图所示,我们只需要从这六个红圈中取极值即可。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VdvFIyQ5-1687342006273)(/mt/P1084/img1.png)]

类似题目推荐

推荐几道贪心题~

LeetCode

LeetCode上的贪心题,代码随想录总结的非常好了,见 贪心 - 代码随想录

CodeFun2000

P1075 拼多多-2023.3.12-飞机大战

P1176 2023.04.08-华为od-第三题-最多等和不相交连续子序列

P1211 塔子大厂真题模拟赛-第一题-魔法石(Ⅰ)

P1070 2023.3.7-百度-第一题-最小化k序列平均值和

代码

CPP

#include <bits/stdc++.h>
using namespace std;

int in() {
    int x;
    cin >> x;
    return x;
}
int main()
{
    int n = in(), k = in();
    int a[n];
    for (int i = 0; i < n; i++) {
        a[i] = in();
    }
    sort(a, a+n);           //排序
    int ans = (int)1e9;
    for (int i = 0; i <= k; i++) {      //枚举修改前i小
        //三者不一定都存在,所以最小值初始化为一个较大的值,最大值初始化为一个小的值,再用三者更新
        int mi = (int)1e9, mx = 0;      
        if(i > 0) {     // 第一段的端点
            mi = min(mi, a[0]*2);
            mx = max(mx, a[i-1]*2);
        }
        if(k-i > 0) {   // 第三段的端点
            mi = min(mi, a[n-(k-i)]/2);
            mx = max(mx, a[n-1]/2);
        }
        if(n != k) {    // 第二段的端点
            mi = min(mi, a[i]);
            mx = max(mx, a[n-(k-i)-1]);
        }
        ans = min(ans, mx - mi);        //更新极差最小值
    }
    cout << ans << endl;
}

python

n, k = map(int, input().split())
a = list(map(int, input().split()))
a.sort()  # 排序
ans = float("inf")
for i in range(k + 1):  # 枚举修改前i小
    # 三者不一定都存在,所以最小值初始化为一个较大的值,最大值初始化为一个小的值,再用三者更新
    mi, mx = float("inf"), 0
    if i > 0:  # 第一段的端点
        mi = min(mi, a[0] * 2)
        mx = max(mx, a[i - 1] * 2)
    if k - i > 0:  # 第三段的端点
        mi = min(mi, a[n - (k - i)] // 2)
        mx = max(mx, a[n - 1] // 2)
    if n != k:  # 第二段的端点
        mi = min(mi, a[i])
        mx = max(mx, a[n - (k - i) - 1])
    ans = min(ans, mx - mi)  # 更新极差最小值
print(ans)

Java

import java.util.*;
class Main {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        int n = in.nextInt(), k = in.nextInt();
        int[] a = new int[n];
        for (int i = 0; i < n; i++) {
            a[i] = in.nextInt();
        }
        Arrays.sort(a);			//排序
        int ans = (int)1e9;
        for (int i = 0; i <= k; i++) {		
            int mi = (int)1e9, mx = 0;		
	        // 第一段的端点
            if(i > 0) {		
                mi = Math.min(mi, a[0]*2);
                mx = Math.max(mx, a[i-1]*2);
            }
       	 	// 第三段的端点
            if(k-i > 0) {	
                mi = Math.min(mi, a[n-(k-i)]/2);
                mx = Math.max(mx, a[n-1]/2);
            }
	        // 第二段的端点
            if(n != k) {	
                mi = Math.min(mi, a[i]);
                mx = Math.max(mx, a[n-(k-i)-1]);
            }
        	// 更新最小极差
            ans = Math.min(ans, mx - mi);		
        }
        System.out.println(ans);
    }
}

Go

package main

import (
    "fmt"
    "sort"
)

func main() {
    var n, k int
    fmt.Scan(&n, &k)

    // 读入序列并排序
    a := make([]int, n)
    for i := 0; i < n; i++ {
        fmt.Scan(&a[i])
    }
    sort.Ints(a)

    // 初始化最小极差为一个较大的值
    ans := int(1e9)

    // 枚举修改前i小的数
    for i := 0; i <= k; i++ {
        // 初始化三个段的端点的最小值和最大值
        mi, mx := int(1e9), 0

        // 第一段的端点
        if i > 0 {
            mi = min(mi, a[0]*2)
            mx = max(mx, a[i-1]*2)
        }

        // 第三段的端点
        if k-i > 0 {
            mi = min(mi, a[n-(k-i)]/2)
            mx = max(mx, a[n-1]/2)
        }

        // 第二段的端点
        if n != k {
            mi = min(mi, a[i])
            mx = max(mx, a[n-(k-i)-1])
        }

        // 更新最小极差
        ans = min(ans, mx-mi)
    }

    // 输出最小极差
    fmt.Println(ans)
}

// 取两个整数的最小值
func min(a, b int) int {
    if a < b {
        return a
    }
    return b
}

// 取两个整数的最大值
func max(a, b int) int {
    if a > b {
        return a
    }
    return b
}

Js

process.stdin.resume();
process.stdin.setEncoding('utf-8');
let input = '';
process.stdin.on('data', (data) => {
    input += data;
    return;
});
process.stdin.on('end', () => {
    const lines = input.trim().split('\n');
    const nk = lines[0].trim().split(' ').map(Number);
    const n = nk[0], k = nk[1];
    const a = lines[1].trim().split(' ').map(Number);
    a.sort((a , b) => {return a - b;});  // 排序
    let ans = 1e9;
    for (let i = 0; i <= k; i++) {  // 枚举修改前i小
        // 三者不一定都存在,所以最小值初始化为一个较大的值,最大值初始化为一个小的值,再用三者更新
        let mi = 1e9, mx = 0;
        if (i > 0) {  // 第一段的端点
            mi = Math.min(mi, a[0] * 2);
            mx = Math.max(mx, a[i - 1] * 2);
        }
        if (k - i > 0) {  // 第三段的端点
            mi = Math.min(mi, a[n - (k - i)] >> 1);
            mx = Math.max(mx, a[n - 1] >> 1);
        }
        if (n !== k) {  // 第二段的端点
            mi = Math.min(mi, a[i]);
            mx = Math.max(mx, a[n - (k - i) - 1]);
        }
        ans = Math.min(ans, mx - mi);  // 更新极差最小值
    }
    console.log(ans);
});
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

塔子哥学算法

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值