失落的宝石
京东技术岗 8月8号笔试 第一题
题目内容
一位探险家在古老遗迹中找到了 nnn 个宝箱,每个宝箱里装有 111 到 666 颗宝石。他统计出所有宝箱的宝石总数为 SSS。
随后,探险家决定带走宝石数量最多的 kkk 个宝箱(若出现并列,他可以任选其中的 kkk 个),将剩余的 n−kn-kn−k 个宝箱留给同伴,这些剩余宝箱的宝石总数为 RRR。
现在已知四个整数 n,k,S,Rn,k,S,Rn,k,S,R,请你帮助探险家重构一组可能的原始宝石序列 v1,v2,...,vnv_1,v_2,\dots,v_nv1,v2,...,vn,要求满足:
- 每个 viv_ivi 均为整数且 1≤vi≤61 \le v_i \le 61≤vi≤6;
- ∑i=1nvi=S\sum_{i=1}^n v_i = S∑i=1nvi=S;
- 从中选出 kkk 个最大值(即最大的 kkk 个元素)后,剩下 n−kn-kn−k 个元素的和恰好为 RRR。
如果不存在符合条件的序列,请指出无解。
约束条件 - 宝箱总数 nnn 满足 2≤n≤200 0002 \le n \le 200\,0002≤n≤200000;
- 取走的宝箱数 kkk 满足 1≤k<n1 \le k < n1≤k<n;
- 总和 SSS 与剩余和 RRR 满足 1≤R<S≤1.2×1061 \le R < S \le 1.2 \times 10^61≤R<S≤1.2×106。
输入描述
输入只有一行,包含四个整数 nnn, kkk, SSS, RRR,以空格分隔。数据保证满足上述约束范围。
输出描述
若存在合法方案,输出一行 nnn 个整数,表示一组合法的宝箱宝石序列,整数之间用空格分隔;否则输出 -1。
样例1
输入
5 2 20 10
输出
4 4 2 6 4
说明
宝箱总数 n=5n=5n=5,取走 k=2k=2k=2,总宝石数 S=20S=20S=20,剩余宝石数 R=10R=10R=10。剩余宝箱数 m=n−k=3m=n-k=3m=n−k=3,取走的宝石数和 T=S−R=10T=S-R=10T=S−R=10。
枚举阈值 ccc:
- 当 c=4c=4c=4 时,剩余和 R=10R=10R=10 满足 3≤10≤3×4=123 \le 10 \le 3 \times 4 = 123≤10≤3×4=12,取走和 T=10T=10T=10 满足 2×4=8≤10≤6×2=122 \times 4=8 \le 10 \le 6 \times 2=122×4=8≤10≤6×2=12,因此 c=4c=4c=4 可行。
构造剩余 333 个宝箱(范围 1,41,41,4):初始每个为 111,剩余调节量 10−3=710-3=710−3=7。依次分配最大值 min(7,3)=3\min(7,3)=3min(7,3)=3,得4;剩余 444,再分配 min(4,3)=3\min(4,3)=3min(4,3)=3,得4;最后剩余 111,分配 111 得2。剩余序列为4 4 2。
构造取走的 222 个宝箱(范围 4,64,64,6):初始每个为 c=4c=4c=4,和为 888,剩余调节量 10−8=210-8=210−8=2。先分配 min(2,2)=2\min(2,2)=2min(2,2)=2,得6;再分配 000 得4。取走序列为6 4。
合并得到完整序列4 4 2 6 4。前 222 大的元素为 666 和 444,剩余元素和 4+4+2=104+4+2=104+4+2=10,且总和为 202020,均满足要求。
样例2
输入
3 1 10 8
输出
-1
说明
宝箱总数 n=3n=3n=3,取走 k=1k=1k=1,总宝石数 S=10S=10S=10,剩余宝石数 R=8R=8R=8。剩余宝箱数 m=2m=2m=2,取走的宝石数和 T=S−R=2T=S-R=2T=S−R=2。
枚举阈值 ccc:
- 剩余和 R=8R=8R=8 要求 m≤R≤m×cm \le R \le m \times cm≤R≤m×c,即 2≤8≤2c2 \le 8 \le 2c2≤8≤2c,解得 c≥4c \ge 4c≥4。
- 取走和 T=2T=2T=2 要求 k×c≤T≤6kk \times c \le T \le 6kk×c≤T≤6k,即 c≤2≤6c \le 2 \le 6c≤2≤6,解得 c≤2c \le 2c≤2。
不存在同时满足 c≥4c \ge 4c≥4 和 c≤2c \le 2c≤2 的整数 ccc,因此无解,输出-1。
样例3
输入
5 3 30 12
输出
6 6 6 6 6
说明
宝箱总数 n=5n=5n=5,取走 k=3k=3k=3,总宝石数 S=30S=30S=30,剩余宝石数 R=12R=12R=12。剩余宝箱数 m=2m=2m=2,取走的宝石数和 T=18T=18T=18。
枚举阈值 ccc:
- 当 c=6c=6c=6 时,剩余和满足 2≤12≤2×6=122 \le 12 \le 2 \times 6=122≤12≤2×6=12,取走和满足 3×6=18≤18≤6×3=183 \times 6=18 \le 18 \le 6 \times 3=183×6=18≤18≤6×3=18,边界刚好成立。
构造剩余 222 个宝箱(范围 1,61,61,6):初始各为 111,剩余调节量 12−2=1012-2=1012−2=10。依次分配 min(10,5)=5\min(10,5)=5min(10,5)=5,得6,再分配 555,得6,得到6 6。取走的 333 个宝箱(范围 6,66,66,6):每个直接为 666,得到6 6 6。
完整序列为全6。最大的 333 个均为 666,剩余 222 个的和为 121212,满足边界条件。
样例4
输入
4 2 12 5
输出
3 2 4 3
说明
宝箱总数 n=4n=4n=4,取走 k=2k=2k=2,总宝石数 S=12S=12S=12,剩余宝石数 R=5R=5R=5。剩余宝箱数 m=2m=2m=2,取走的宝石数和 T=7T=7T=7。
枚举阈值 ccc:
- c=3c=3c=3 时,剩余和 555 满足 2≤5≤2×3=62 \le 5 \le 2 \times 3=62≤5≤2×3=6,取走和 777 满足 2×3=6≤7≤122 \times 3=6 \le 7 \le 122×3=6≤7≤12,可行。
剩余 222 个宝箱(范围 1,31,31,3):初始各 111,调节量 5−2=35-2=35−2=3。先分配 min(3,2)=2\min(3,2)=2min(3,2)=2 得3,再分配 111 得2,序列为3 2。取走 222 个宝箱(范围 3,63,63,6):初始各 333 和为 666,调节量 7−6=17-6=17−6=1。第一个分配 111 得4,第二个得3,序列为4 3。
完整序列为3 2 4 3。最大的 222 个为 444 和 333,剩余和为 3+2=53+2=53+2=5,总和为 121212,符合要求。
题解和思路
思路
实现思路:逻辑分析
- 为了让拿走部分宝石最小值尽可能大,必须均匀分配。为了让保留宝石的最大值尽可能少,必须均匀分配。这一步是
保证拿走的最小值必须大于等于保留最大值 - 判断是否能构成包含以下方面:
- 是否存在宝箱宝石数量为0
- 是否存在平均分配后,宝石数量大于6
- 是否均匀分配后,拿走宝箱最小宝石数量小于留下宝箱最大宝石数量
- 接下来就比较简单了,先分配拿走宝箱再分配留下宝箱,两种都可通过取余得出超过各自平均值的数量,给指定数量分配平均值 + 1的数量即可。
- 算法整体时间复杂度为
O(n)
C++
cpp
#include<bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n,k,S,R;
cin >> n >> k >> S >> R;
// 剩余数量
int T = (n - k);
// 基本数量不满足每个宝箱至少 1 颗
if (S - R < k || R < T) {
cout << -1 << endl;
return 0;
}
int takeAverage = (S - R) / k;
int exceedAverageCount = (S - R) % k;
// 向上取整
int remaingUpAverage = (R + T - 1) / T;
int remaingAverage = R / T;
// 无法满足分配 拿走最小值小于留下最大值 同时检验上边界是否满足 <=6
if (takeAverage < remaingUpAverage || (takeAverage + (exceedAverageCount > 0 ? 1 : 0)) > 6 || remaingUpAverage > 6 ) {
cout << -1 << endl;
return 0;
}
// 构建结果
vector<int> ans(n);
// 先处理被带走的
for (int i = 0; i < k; i++) {
ans[i] = takeAverage;
if (exceedAverageCount > 0) {
ans[i]++;
exceedAverageCount--;
}
}
// 处理剩余
int exceedTakeAverageCount = R % T;
for (int i = k; i < n; i++) {
ans[i] = remaingAverage;
if (exceedTakeAverageCount > 0) {
ans[i]++;
exceedTakeAverageCount--;
}
}
// 输出结果
for (int i = 0; i < n; i++) {
if (i > 0) {
cout << " ";
}
cout << ans[i];
}
return 0;
}
Java
java
import java.io.*;
import java.util.*;
public class Main {
public static void main(String[] args) throws Exception {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st = new StringTokenizer(br.readLine());
int n = Integer.parseInt(st.nextToken());
int k = Integer.parseInt(st.nextToken());
int S = Integer.parseInt(st.nextToken());
int R = Integer.parseInt(st.nextToken());
// 剩余数量
int T = n - k;
// 基本数量不满足每个宝箱至少 1 颗
if (S - R < k || R < T) {
System.out.println(-1);
return;
}
int takeAverage = (S - R) / k;
int exceedAverageCount = (S - R) % k;
// 向上取整
int remaingUpAverage = (R + T - 1) / T;
int remaingAverage = R / T;
// 无法满足分配
// 拿走最小值小于留下最大值
// 同时检验上边界是否满足 <=6
if (takeAverage < remaingUpAverage ||
takeAverage + (exceedAverageCount > 0 ? 1 : 0) > 6 ||
remaingUpAverage > 6) {
System.out.println(-1);
return;
}
// 构建结果
int[] ans = new int[n];
// 先处理被带走的
for (int i = 0; i < k; i++) {
ans[i] = takeAverage;
if (exceedAverageCount > 0) {
ans[i]++;
exceedAverageCount--;
}
}
// 处理剩余
int exceedTakeAverageCount = R % T;
for (int i = k; i < n; i++) {
ans[i] = remaingAverage;
if (exceedTakeAverageCount > 0) {
ans[i]++;
exceedTakeAverageCount--;
}
}
// 输出结果
StringBuilder sb = new StringBuilder();
for (int i = 0; i < n; i++) {
if (i > 0) {
sb.append(" ");
}
sb.append(ans[i]);
}
System.out.println(sb);
}
}
python
python
n, k, S, R = map(int, input().split())
# 剩余数量
T = n - k
# 基本数量不满足每个宝箱至少 1 颗
if S - R < k or R < T:
print(-1)
exit()
takeAverage = (S - R) // k
exceedAverageCount = (S - R) % k
# 向上取整
remaingUpAverage = (R + T - 1) // T
remaingAverage = R // T
# 无法满足分配
# 拿走最小值小于留下最大值
# 同时检验上边界是否满足 <=6
if (takeAverage < remaingUpAverage or
takeAverage + (1 if exceedAverageCount > 0 else 0) > 6 or
remaingUpAverage > 6):
print(-1)
exit()
# 构建结果
ans = [0] * n
# 先处理被带走的
for i in range(k):
ans[i] = takeAverage
if exceedAverageCount > 0:
ans[i] += 1
exceedAverageCount -= 1
# 处理剩余
exceedTakeAverageCount = R % T
for i in range(k, n):
ans[i] = remaingAverage
if exceedTakeAverageCount > 0:
ans[i] += 1
exceedTakeAverageCount -= 1
# 输出结果
print(" ".join(map(str, ans)))
Javascript
js
const readline = require("readline");
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
rl.on("line", (line) => {
const [n, k, S, R] = line.trim().split(/\s+/).map(Number);
// 剩余数量
const T = n - k;
// 基本数量不满足每个宝箱至少 1 颗
if (S - R < k || R < T) {
console.log(-1);
rl.close();
return;
}
const takeAverage = Math.floor((S - R) / k);
let exceedAverageCount = (S - R) % k;
// 向上取整
const remaingUpAverage = Math.floor((R + T - 1) / T);
const remaingAverage = Math.floor(R / T);
// 无法满足分配
// 拿走最小值小于留下最大值
// 同时检验上边界是否满足 <=6
if (takeAverage < remaingUpAverage ||
takeAverage + (exceedAverageCount > 0 ? 1 : 0) > 6 ||
remaingUpAverage > 6) {
console.log(-1);
rl.close();
return;
}
// 构建结果
const ans = new Array(n).fill(0);
// 先处理被带走的
for (let i = 0; i < k; i++) {
ans[i] = takeAverage;
if (exceedAverageCount > 0) {
ans[i]++;
exceedAverageCount--;
}
}
// 处理剩余
let exceedTakeAverageCount = R % T;
for (let i = k; i < n; i++) {
ans[i] = remaingAverage;
if (exceedTakeAverageCount > 0) {
ans[i]++;
exceedTakeAverageCount--;
}
}
// 输出结果
console.log(ans.join(" "));
rl.close();
});
Go
go
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
in := bufio.NewReader(os.Stdin)
out := bufio.NewWriter(os.Stdout)
defer out.Flush()
var n, k, S, R int
fmt.Fscan(in, &n, &k, &S, &R)
// 剩余数量
T := n - k
// 基本数量不满足每个宝箱至少 1 颗
if S-R < k || R < T {
fmt.Fprintln(out, -1)
return
}
takeAverage := (S - R) / k
exceedAverageCount := (S - R) % k
// 向上取整
remaingUpAverage := (R + T - 1) / T
remaingAverage := R / T
// 无法满足分配
// 拿走最小值小于留下最大值
// 同时检验上边界是否满足 <=6
if takeAverage < remaingUpAverage ||
takeAverage+(func() int {
if exceedAverageCount > 0 {
return 1
}
return 0
}()) > 6 ||
remaingUpAverage > 6 {
fmt.Fprintln(out, -1)
return
}
// 构建结果
ans := make([]int, n)
// 先处理被带走的
for i := 0; i < k; i++ {
ans[i] = takeAverage
if exceedAverageCount > 0 {
ans[i]++
exceedAverageCount--
}
}
// 处理剩余
exceedTakeAverageCount := R % T
for i := k; i < n; i++ {
ans[i] = remaingAverage
if exceedTakeAverageCount > 0 {
ans[i]++
exceedTakeAverageCount--
}
}
// 输出结果
for i := 0; i < n; i++ {
if i > 0 {
fmt.Fprint(out, " ")
}
fmt.Fprint(out, ans[i])
}
}