【软盘分配】

566 篇文章

已下架不支持订阅

472 篇文章

已下架不支持订阅

题目描述

计算机刚刚兴起时,移动存储通常采用软盘。软盘的优点是便宜,但是容量很小。

现在有一张精美图片,大小为 d 兆,同时阿甘手中有 n 种不同容量的软盘,假设第 i 种软盘的容量为 2^i 兆,有 N[i] 个。

请你帮助阿甘分配软盘,实现图片最多拷贝的同时软盘减少浪费,并告诉阿甘可以拷贝多少份图片,以及剩余未使用的软盘数量。

注意:图片可以在压缩后,进行分割,得到多个子压缩包。一个软盘只能用于存储同一个图片文件的子压缩包。

输入描述

第一行输入图片的大小 d 兆。

第二行输入 n 个正整数,表示 n 种不同容量的软盘的数量,其中第 i 种软盘的容量为 2^i。以逗号分隔。n 不大于 30。

输出描述

第一行输出最多可拷贝几份图片。

第二行输出剩余软盘数量,格式见用例。

用例

输入

18

[0, 2, 2, 9, 8]

输出9
[0, 0, 0, 2, 0]
说明

有 1 兆软盘 0 个。

有 2 兆软盘 2 个。

有 4 兆软盘 2 个。

有 8 兆软盘 9 个。

有 16 兆软盘 8 个。

其中 16 + 2 = 18,因此消耗2个2兆软盘,和2个16兆软盘可以拷贝2份图片。

16 + 4 = 20,因此消耗2个4兆软盘,和2个16兆软盘可以拷贝2份图片。

16 + 8 = 24,因此消耗4个8兆软盘,和4个16兆软盘可以拷贝4份图片。

8 + 8 + 8 = 24,因此可以消耗3个8兆软盘,拷贝1分图片。

一共可以拷贝出9份图片。剩余2个8兆软盘未使用。

题目解析

想要实现图片的最多拷贝,那么就要尽可能构造出容量接近于(≥) d 的软盘组合。

对于用例:

30
[10, 5, 0, 1, 3, 2]

图片大小 d = 30 兆,其可以分解为:16 + 8 + 4 + 2,因此我们可以选择下面软盘组合来实现无浪费地存储

1 个 2^4 兆的软盘,1 个 2^3 兆的软盘,1 个 2^2 的软盘,1 个 2^1 的软盘。

但是由于没有 2^2 兆的软盘,因此我们可以将对于 2^2 兆软盘的需求降级,变为 2 个 2^1 兆的软盘,也就是说最终选择是:

1 个 2^4 兆的软盘,1 个 2^3 兆的软盘,3 个 2^1 兆的软盘。

即 16 + 8 + 3 * 2 = 30

另外,如果单个软盘容量就能满足 d,比如 2^5 兆(32兆)的软盘也可以存储 d = 30 兆的图片,虽然此时浪费了一些,但是一个软盘只能给一个图片,因此为了避免更大的浪费,32 兆软盘就可以单独组合,而不需要组合其他软盘使用。

那么如何能准确的构造出容量为 30 兆的软盘组合呢?

首先,我们将 d 值转为二进制,然后再反序,让 d 和软盘列表 N 的阶数方向(从左到右,依次是 2^0, 2^1, ..., 2^i)保持一致,

比如 d=30,可以转为 [0, 1, 1, 1, 1] 的反序二进制值的个数数组,该数组的含义是

  • 2^0有0个
  • 2^1有1个
  • 2^2有1个
  • 2^3有1个
  • 2^4有1个

而输入的第二行 N 列表,也可以看成反序二进制数的个数数组: [10, 5, 0, 1, 3, 2]

该数组的含义是:

  • 2^0有10个
  • 2^1有5个
  • 2^2有0个
  • 2^3有1个
  • 2^4有3个
  • 2^5有2个

如果想从 N 列表中选出和为 d 的软盘组合,那么不就是 N 和 d 的二进制值个数求差吗?

我们只需要比较D.length范围的内,而超出D.length范围的N[i]其实就是单个软盘就足以满足一个图片存储。

如上图所示,如果每次求差后,N[0] >= 0,则表示可以构造出一个和为30的软盘组合。

但是N[0] < 0 只能表示无法构造出一个和准确为30的的软盘组合,但是却还是有可能构造出一个和大于30的信道组合

程序输入

47
[10, 5, 0, 1, 3, 2]

对应的N和D如下

直到N[i]完全抵消了负值结束,然后count++。也可能N[i]到最后也没有抵消完负值,此时说明无法构造出一个和大于30的软盘组合,整个程序结束。


2023.10.10

上面举得例子

47
[10, 5, 0, 1, 3, 2]

进行二进制减法时存在问题

如下图所示,在进行第二次减法时

之前的策略时,总是向低位借,因此最后借债都会压到被减数的阶0上,之后再将阶0上的借债,转移不停转移到高位,因此会得到结果如下

但是这种策略不是最优的,因为我们可以看下图

其中 被减数的标黄部分,经过计算,是可以确定小于 减数的标黄部分的,因此即使我们向低位借,最终还是不够,最后反过来还是要向高位借。

而二进制数,一个高位是可以直接满足前面的所有低位的,什么意思呢?看下图

我们此时直接从被减数红框高位中借1,就可以满足 减数红框中所有位之和。

此时被减数的 绿色低位部分 得到了保留,避免了浪费。

Python算法源码

# 输入获取
D = int(input())
N = eval(input())


def calc_sub(bin1):
    ans = 0
    for i in range(len(bin1)):
        ans += bin1[i] * (1 << i)
    return ans


def binary_sub(minuend, subtrahend):
    """
    二进制减法
    :param minuend: 被减数
    :param subtrahend: 减数
    :return: 被减数是否为正数
    """
    # 进行减法运算逻辑, 从高位开始
    i = len(minuend) - 1
    while i >= 0:
        if minuend[i] >= subtrahend[i]:
            # 如果对应位的软盘数足够,则直接相减
            minuend[i] -= subtrahend[i]
        else:
            # 如果对应位的软盘数不足,此时有两种策略,一是向低位借,一是向高位借
            # 具体向哪里借,需要看 minuend 的 [0,i] 低位部分是否能够承载 subtrahend[0, i] 低位部分
            if calc_sub(minuend[0:i + 1]) < calc_sub(subtrahend[0:i + 1]):
                # 如果minuend 的 [0,i]不能承载,则向高位借,即从j=i+1位开始借
                j = i + 1
                while j < len(minuend):
                    if minuend[j] > 0:
                        # 如果高位 j 有软盘可借,则借
                        minuend[j] -= 1
                        return True
                    else:
                        # 否则继续向更高位探索
                        minuend[j] += subtrahend[j]
                        j += 1
                # 如果所有高位都没有富余软盘数,则说明减法结果为负数
                return False
            else:
                # 如果minuend 的 [0,i]可以承载,则向低位借(可以避免浪费)
                # 此时minuend[i]为负数,表示欠债
                minuend[i] -= subtrahend[i]

                # 将当前阶位的欠债,转移到前面的低阶位上,注意转移时,欠债x2
                minuend[i - 1] += minuend[i] << 1

                # 转移后,当前阶位的欠债变为0
                minuend[i] = 0

        i -= 1

    return True


if __name__ == '__main__':
    # 将D值转化为二进制形式,并且为了和N[]的阶位进行对应,这里将D的二进制进行了反转
    subtrahend = list(map(int, str(bin(D))[2:]))
    subtrahend.reverse()

    # count记录N能承载几个D
    count = 0

    # N中高阶软盘的单个软盘就能满足D,因此这些高阶软盘有几个,即能承载几个D
    for i in range(len(subtrahend), len(N)):
        # R ~ subtrahend.length 阶的单个软盘就能承载一个D,因此这些软盘有几个,就能承载几个D
        count += N[i]
        N[i] = 0

    # 0 ~ subtrahend.length - 1 阶的单个软盘无法承载一个D,因此这些阶需要组合起来才能承载一个D
    minuend = N[0:len(subtrahend)]
    while len(minuend) < len(subtrahend):
        minuend.append(0)

    # 进行二进制减法
    while binary_sub(minuend, subtrahend):
        count += 1

    for i in range(min(len(subtrahend), len(N))):
        N[i] = minuend[i]

    print(count)
    print(f"[{", ".join(map(str, N))}]")

Java算法源码

import java.util.Arrays;
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        int D = Integer.parseInt(sc.nextLine());
        int[] N = parse(sc.nextLine());

        // 将D值转化为二进制形式,并且为了和N的阶位进行对应,这里将D的二进制进行了反转
        int[] subtrahend =
                Arrays.stream(new StringBuilder(Integer.toBinaryString(D)).reverse().toString().split(""))
                        .mapToInt(Integer::parseInt)
                        .toArray();

        // count记录N能承载几个D
        int count = 0;

        // N中高阶软盘的单个软盘就能满足D,因此这些高阶软盘有几个,即能承载几个D
        for (int i = N.length - 1; i >= subtrahend.length; i--) {
            // R ~ subtrahend.length 阶的单个软盘就能承载一个D,因此这些软盘有几个,就能承载几个D
            count += N[i];
            N[i] = 0;
        }

        // 0 ~ subtrahend.length - 1 阶的单个软盘无法承载一个D,因此这些阶需要组合起来才能承载一个D
        int[] minuend = Arrays.copyOfRange(N, 0, subtrahend.length);

        // 进行二进制减法
        while (binary_sub(minuend, subtrahend)) {
            count++;
        }

        for (int i = 0; i < Math.min(subtrahend.length, N.length); i++) {
            N[i] = minuend[i];
        }

        System.out.println(count);
        System.out.println(Arrays.toString(N));
    }

    public static int[] parse(String s) {
        return Arrays.stream(s.substring(1, s.length() - 1).split(", ")).mapToInt(Integer::parseInt).toArray();
    }

    /**
     * 二进制减法
     *
     * @param minuend    被减数
     * @param subtrahend 减数
     * @return 被减数是否为正数
     */
    public static boolean binary_sub(int[] minuend, int[] subtrahend) {
        // 进行减法运算逻辑, 从高位开始
        for (int i = minuend.length - 1; i >= 0; i--) {

            if (minuend[i] >= subtrahend[i]) {
                // 如果对应位的软盘数足够,则直接相减
                minuend[i] -= subtrahend[i];
            } else {
                // 如果对应位的软盘数不足,此时有两种策略,一是向低位借,一是向高位借
                // 具体向哪里借,需要看 minuend 的 [0,i] 低位部分是否能够承载 subtrahend[0, i] 低位部分
                if (calc_bin(Arrays.copyOfRange(minuend, 0, i + 1))
                        < calc_bin(Arrays.copyOfRange(subtrahend, 0, i + 1))) {
                    // 如果minuend 的 [0,i]不能承载,则向高位借,即从j=i+1位开始借
                    int j = i + 1;
                    while (j < minuend.length) {
                        if (minuend[j] > 0) {
                            // 如果高位 j 有软盘可借,则借
                            minuend[j] -= 1;
                            return true;
                        } else {
                            // 否则继续向更高位探索
                            minuend[j] += subtrahend[j];
                            j += 1;
                        }
                    }
                    // 如果所有高位都没有富余软盘数,则说明减法结果为负数
                    return false;
                } else {
                    // 如果minuend 的 [0,i]可以承载,则向低位借(向低位借,可以避免浪费)
                    // 此时minuend[i]为负数,表示欠债
                    minuend[i] -= subtrahend[i];

                    // 将当前阶位的欠债,转移到前面的低阶位上,注意转移时,欠债x2
                    minuend[i - 1] += minuend[i] << 1;

                    // 转移后,当前阶位的欠债变为0
                    minuend[i] = 0;
                }
            }
        }

        return true;
    }

    public static int calc_bin(int[] bin) {
        int ans = 0;
        for (int i = 0; i < bin.length; i++) {
            ans += bin[i] * (1 << i);
        }
        return ans;
    }
}

JS算法源码

const rl = require("readline").createInterface({ input: process.stdin });
var iter = rl[Symbol.asyncIterator]();
const readline = async () => (await iter.next()).value;

void (async function () {
  const D = parseInt(await readline());
  const N = JSON.parse(await readline());

  // 将D值转化为二进制形式,并且为了和N[]的阶位进行对应,这里将D的二进制进行了反转
  const subtrahend = Number(D).toString(2).split("").map(Number).reverse();

  // count记录N能承载几个D
  let count = 0;

  // N中高阶软盘的单个软盘就能满足D,因此这些高阶软盘有几个,即能承载几个D
  for (let i = N.length - 1; i >= subtrahend.length; i--) {
    // R ~ subtrahend.length 阶的单个软盘就能承载一个D,因此这些软盘有几个,就能承载几个D
    count += N[i];
    N[i] = 0;
  }

  // 0 ~ subtrahend.length - 1 阶的单个软盘无法承载一个D,因此这些阶需要组合起来才能承载一个D
  const minuend = N.slice(0, subtrahend.length);
  while (minuend.length < subtrahend.length) {
    minuend.push(0);
  }

  // 进行二进制减法
  while (binary_sub(minuend, subtrahend)) {
    count++;
  }

  for (let i = 0; i < Math.min(subtrahend.length, N.length); i++) {
    N[i] = minuend[i];
  }

  console.log(count);
  console.log(`[${N.join(", ")}]`);
})();

/**
 * 进行二进制减法
 * @param {*} minuend 被减数
 * @param {*} subtrahend 减数
 * @returns 被减数是否为正数
 */
function binary_sub(minuend, subtrahend) {
  // 进行减法运算逻辑, 从高位开始
  for (let i = minuend.length - 1; i >= 0; i--) {
    if (minuend[i] >= subtrahend[i]) {
      // 如果对应位的软盘数足够,则直接相减
      minuend[i] -= subtrahend[i];
    } else {
      // 如果对应位的软盘数不足,此时有两种策略,一是向低位借,一是向高位借
      // 具体向哪里借,需要看 minuend 的 [0,i] 低位部分是否能够承载 subtrahend[0, i] 低位部分
      if (
        calc_sub(minuend.slice(0, i + 1)) < calc_sub(subtrahend.slice(0, i + 1))
      ) {
        // 如果minuend 的 [0,i]不能承载,则向高位借,即从j=i+1位开始借
        let j = i + 1;
        while (j < minuend.length) {
          if (minuend[j] > 0) {
            // 如果高位 j 有软盘可借,则借
            minuend[j] -= 1;
            return true;
          } else {
            // 否则继续向更高位探索
            minuend[j] += subtrahend[j];
            j += 1;
          }
        }
        // 如果所有高位都没有富余软盘数,则说明减法结果为负数
        return false;
      } else {
        // 如果minuend 的 [0,i]可以承载,则向低位借(向低位借,可以避免浪费)
        // 此时minuend[i]为负数,表示欠债
        minuend[i] -= subtrahend[i];

        // 将当前阶位的欠债,转移到前面的低阶位上,注意转移时,欠债x2
        minuend[i - 1] += minuend[i] << 1;

        // 转移后,当前阶位的欠债变为0
        minuend[i] = 0;
      }
    }
  }

  return true;
}

function calc_sub(bin) {
  let ans = 0;
  for (let i = 0; i < bin.length; i++) {
    ans += bin[i] * (1 << i);
  }
  return ans;
}

C算法源码

#include <stdio.h>
#include <math.h>

#define MAX_SIZE 50

int calc_bin(const int bin[], int bin_size) {
    int ans = 0;
    for (int i = 0; i < bin_size; i++) {
        ans += bin[i] * (1 << i);
    }
    return ans;
}

/*!
 *
 * @param minuend 被减数
 * @param subtrahend 减数
 * @param size 被减数和件数的二进制位数
 * @return 减法运算后,被减数是否为正数
 */
int binary_sub(int minuend[], int subtrahend[], int size) {
    // 进行减法运算逻辑, 从高位开始
    for (int i = size - 1; i >= 0; i--) {
        if (minuend[i] >= subtrahend[i]) {
            // 如果对应位的软盘数足够,则直接相减
            minuend[i] -= subtrahend[i];
        } else {
            // 如果对应位的软盘数不足,此时有两种策略,一是向低位借,一是向高位借
            // 具体向哪里借,需要看 minuend 的 [0,i] 低位部分是否能够承载 subtrahend[0, i] 低位部分
            if (calc_bin(minuend, i + 1) < calc_bin(subtrahend, i + 1)) {
                // 如果minuend 的 [0,i]不能承载,则向高位借,即从j=i+1位开始借
                int j = i + 1;
                while (j < size) {
                    if (minuend[j] > 0) {
                        // 如果高位 j 有软盘可借,则借
                        minuend[j] -= 1;
                        return 1;
                    } else {
                        // 否则继续向更高位探索
                        minuend[j] += subtrahend[j];
                        j += 1;
                    }
                }
                // 如果所有高位都没有富余软盘数,则说明减法结果为负数
                return 0;
            } else {
                // 如果minuend 的 [0,i]可以承载,则向低位借(向低位借,可以避免浪费)
                // 此时minuend[i]为负数,表示欠债
                minuend[i] -= subtrahend[i];
                // 将当前阶位的欠债,转移到前面的低阶位上,注意转移时,欠债x2
                minuend[i - 1] += minuend[i] << 1;
                // 转移后,当前阶位的欠债变为0
                minuend[i] = 0;
            }
        }
    }

    return 1;
}


int main() {
    int D;
    scanf("%d", &D);

    int N[MAX_SIZE] = {0};
    int N_size = 0;

    getchar();
    getchar();
    while (scanf("%d", &N[N_size++])) {
        if (getchar() != ',') break;
        getchar();
    }

    int subtrahend[MAX_SIZE];
    int subtrahend_size = 0;

    // 将D值转化为二进制形式,并且为了和N[]的阶位进行对应,这里将D的二进制进行了反转
    while (D > 0) {
        subtrahend[subtrahend_size++] = D % 2;
        D /= 2;
    }

    // count记录N能承载几个D
    int count = 0;

    // N中高阶软盘的单个软盘就能满足D,因此这些高阶软盘有几个,即能承载几个D
    for (int i = N_size - 1; i >= subtrahend_size; i--) {
        // R ~ subtrahend.length 阶的单个软盘就能承载一个D,因此这些软盘有几个,就能承载几个D
        count += N[i];
        N[i] = 0;
    }

    // 0 ~ subtrahend.length - 1 阶的单个软盘无法承载一个D,因此这些阶需要组合起来才能承载一个D
    int minuend[subtrahend_size];
    for (int i = 0; i < subtrahend_size; i++) {
        minuend[i] = N[i];
    }

    // 进行二进制减法
    while (binary_sub(minuend, subtrahend, subtrahend_size)) {
        count++;
    }

    for (int i = 0; i < fmin(subtrahend_size, N_size); i++) {
        N[i] = minuend[i];
    }

    printf("%d\n", count);

    printf("[");
    for (int i = 0; i < N_size; i++) {
        printf("%d", N[i]);
        if (i < N_size - 1) {
            printf(", ");
        }
    }
    printf("]");

    return 0;
}

C++算法源码

#include <bits/stdc++.h>

using namespace std;

// 二进制转十进制
int bin2dec(vector<int> bin) {
    int ans = 0;

    for (int i = 0; i < bin.size(); i++) {
        ans += bin[i] * (1 << i);
    }

    return ans;
}

// 十进制转二进制
vector<int> dec2bin(int dec) {
    vector<int> bin;

    while (dec > 0) {
        bin.emplace_back(dec % 2);
        dec >>= 1;
    }

    return bin;
}

/*!
 * 二进制减法
 * @param minuend 被减数
 * @param subtrahend 减数
 * @return 被减数是否为正数
 */
bool binarySub(vector<int> &minuend, vector<int> &subtrahend) {
    // 进行减法运算逻辑, 从高位开始
    for (int i = minuend.size() - 1; i >= 0; i--) {
        if (minuend[i] >= subtrahend[i]) {
            // 如果对应位的软盘数足够,则直接相减
            minuend[i] -= subtrahend[i];
        } else {
            // 如果对应位的软盘数不足,此时有两种策略,一是向低位借,一是向高位借
            // 具体向哪里借,需要看 minuend 的 [0,i] 低位部分是否能够承载 subtrahend[0, i] 低位部分
            if (bin2dec(vector<int>(minuend.begin(), minuend.begin() + i + 1)) <
                bin2dec(vector<int>(subtrahend.begin(), subtrahend.begin() + i + 1))) {
                // 如果minuend 的 [0,i]不能承载,则向高位借,即从j=i+1位开始借
                int j = i + 1;
                while (j < minuend.size()) {
                    if (minuend[j] > 0) {
                        // 如果高位 j 有软盘可借,则借
                        minuend[j] -= 1;
                        return true;
                    } else {
                        // 否则继续向更高位探索
                        minuend[j] += subtrahend[j];
                        j += 1;
                    }
                }

                // 如果所有高位都没有富余软盘数,则说明减法结果为负数
                return false;
            } else {
                // 如果minuend 的 [0,i]可以承载,则向低位借(向低位借,可以避免浪费)
                // 此时minuend[i]为负数,表示欠债
                minuend[i] -= subtrahend[i];
                // 将当前阶位的欠债,转移到前面的低阶位上,注意转移时,欠债x2
                minuend[i - 1] += minuend[i] << 1;
                // 转移后,当前阶位的欠债变为0
                minuend[i] = 0;
            }
        }
    }

    return true;
}

int main() {
    int D;
    cin >> D;
    getchar();

    vector<int> N;

    getchar();
    int Ni;
    while (cin >> Ni) {
        N.emplace_back(Ni);
        if (getchar() != ',') break;
        getchar();
    }

    // D值作为减数subtrahend
    // 将D值转化为二进制形式,并且为了和N[]的阶位进行对应,这里将D的二进制进行了反转
    vector<int> subtrahend = dec2bin(D);

    // N值作为被减数minuend
    vector<int> minuend(subtrahend.size(), 0);

    // count记录N能承载几个D
    int count = 0;

    for (int i = 0; i < N.size(); i++) {
        if (i >= subtrahend.size()) {
            // N 中 R ~ subtrahend.length 阶的单个软盘就能承载一个D,因此这些软盘有几个,就能承载几个D
            count += N[i];
            N[i] = 0;
        } else {
            // N 中 0 ~ min(R, subtrahend.length) 阶的软盘需要多个阶组合才能满足一个D
            minuend[i] = N[i];
        }
    }

    // 进行二进制减法
    while (binarySub(minuend, subtrahend)) {
        count++;
    }

    for (int i = 0; i < min(subtrahend.size(), N.size()); i++) {
        N[i] = minuend[i];
    }

    cout << count << endl;

    cout << "[";
    for (int i = 0; i < N.size(); i++) {
        cout << N[i];
        if (i < N.size() - 1) {
            cout << ", ";
        }
    }
    cout << "]";

    return 0;
}

已下架不支持订阅

评论 35
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

程序员阿甘

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

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

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

打赏作者

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

抵扣说明:

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

余额充值