题目内容
塔子哥是一个收藏家,他喜欢收集各种珍奇的物品。最近,他在旅游时在俄罗斯购买了一些俄罗斯套娃。他深深地被这些绚丽多彩的小玩意吸引住了,每天都会花费大量的时间玩弄它们。随着时间的推移,他逐渐发现将套娃放在其他套娃内是一个有趣的游戏,并决定挑战自己,看看他是否能以最小的成本套上所有的俄罗斯套娃。
具体的,塔子哥有 n 个俄罗斯套娃,第 i 个俄罗斯套娃的大小为 ,内部空间为
和一个价值
。
对于两个俄罗斯套娃 x 和 y , x 能放入 y 中当且仅当 ,且放入后会占据 y 大小为
的内部空间,即y 的内部空间剩下
,每个俄罗斯套娃只能放在另外的一个俄罗斯套娃内,每个俄罗斯套娃内部也只能放一个俄罗斯套娃(当然内部放的这个俄罗斯套娃可以内部还有俄罗斯套娃)。
显然俄罗斯套娃是套的越多越好,如果套完之后俄罗斯套娃 i 还剩 k 的内部空间塔子哥需要付出 的花费,总花费为所有俄罗斯套娃的花费之和。
现在塔子哥想知道最小的花费为多少?
输入描述
第一行一个正整数 n ,表示俄罗斯套娃的个数
接下来三行每行 n 个整数,分别为
a1,a2,...,an b1,b2,...,bn c1,c2,...,cn
,
,
输出描述
输出一个整数表示最小的花费。
样例
输入
3 5 4 3 4 2 2 3 2 1
输出
6
样例解释
将第二个俄罗斯套娃放在第一个里面,第三个不动,这样第一个俄罗斯套娃剩 0 的空间,第二个剩 2 ,第三个剩 2 。总花费为 0*3+2*2+2*1=6 。
可以证明这是花费最小的方案。
思路
贪心 + multiset(平衡树)
每个物品中能放至多一个其他的物品,且每个物品只能放在至多一个物品内。
先考虑单位空间价值更大的,让他们内部的空间先被填充。
按照单位空间价值从大到小枚举每个物品,对每个物品找到一个体积小于当前枚举物品的空间的物品。即找到一个小于等于 x 的最大的 y 值。
因为物品的体积可能相同,故需要一个可以存储重复元素的 multiset,其中加入了所有的物品体积。
这里需要注意的是,一个物品的体积和空间可能相同,故我们需要判断获取的小于 x 的最大的 y 值是否可能和当前枚举的物品的空间相同:
-
如果相同则可能是同一物品,故需要判断这个体积大小的物品的数量
-
如果恰好只有一个,则说明是自己,则需要找到更小的
-
如果不止一个,则说明存在其他的和当前枚举的物品空间大小相同的物品 i ,将物品 i 放入到当前物品中即可。
-
此外,我们每将一个物品放到另一个物品的空间中之后,就需要删除这个物品,防止多次使用,因此上述的重复判断需要有一个额外的标记,下面以 gt1 作为每个体积的物品数量是否大于 1 的标记。
时间复杂度:O(n\log n)
类似题目推荐
LeetCode
set/multiset 练习题
代码
CPP
#include <bits/stdc++.h>
using namespace std;
const int N = 100010;
struct Node {
int a, b, c;
}q[N];
// 每个值的剩余数量
int cnt[N];
// 一个数的初始数量是否大于 1
bool gt1[N];
int n;
int main()
{
long long ans = 0;
multiset<int> S;
scanf("%d", &n);
for (int i = 1; i <= n; ++i) {
scanf("%d", &q[i].a);
// 如果一个值 q[i].a 的总数量大于 1,则 gt1[q[i].a] = true
if (++cnt[q[i].a] > 1) {
gt1[q[i].a] = true;
}
S.insert(q[i].a);
}
for (int i = 1; i <= n; ++i) scanf("%d", &q[i].b);
for (int i = 1; i <= n; ++i) {
// 初始化所有的剩余空间都是满的
scanf("%d", &q[i].c);
ans += 1ll * q[i].c * q[i].b;
}
// 按照每个空间的单位价值从大到小排序,先处理单位价值更大的
sort(q + 1, q + n + 1, [](const Node& A, const Node& B) {
return A.c > B.c;
});
for (int i = 1; i <= n; ++i) {
// 找到小于等于 q[i].b 的最大
auto it = S.upper_bound(q[i].b);
if (it == S.begin()) continue;
it--;
// 如果选择的物品的体积小于当前物品的空间
// 或者选择的物品的体积等于当前物品的空间,但是这个空间的总数量大于 1
if (*it != q[i].a || gt1[*it]) {
ans -= 1ll * (*it) * q[i].c;
S.erase(it);
} else {
// 否则就得继续往前找体积小于当前物品的空间的物品
if (it == S.begin()) continue;
it--;
ans -= 1ll * (*it) * q[i].c;
S.erase(it);
}
}
printf("%lld\n", ans);
return 0;
}
python
from sortedcontainers import SortedSet N = 100010 # 每个值的剩余数量 cnt = [0] * N # 一个数的初始数量是否大于 1 gt1 = [False] * N S = SortedSet() n = int(input()) a = list(map(int, input().split())) b = list(map(int, input().split())) c = list(map(int, input().split())) ans = 0 for i in range(n): cnt[a[i]] += 1 # 如果一个值 q[i].a 的总数量大于 1,则 gt1[q[i].a] = True if cnt[a[i]] > 1: gt1[a[i]] = True # 初始化所有的剩余空间都是满的 ans += b[i] * c[i] S.add(a[i]) idx = [i for i in range(n)] # 按照每个空间的单位价值从大到小排序,先处理单位价值更大的 idx.sort(key=lambda x: -c[x]) for i in range(n): j = idx[i] # 找到小于等于 q[i].b 的最大 it = S.bisect_right(b[j]) if it == 0: continue it -= 1 val = S[it] # 如果选择的物品的体积小于当前物品的空间 # 或者选择的物品的体积等于当前物品的空间,但是这个空间的总数量大于 1 if val != a[j] or gt1[val]: ans -= val * c[j] S.remove(val) else: # 否则就得继续往前找体积小于当前物品的空间的物品 if it == 0: continue it -= 1 val = S[it] ans -= val * c[j] S.remove(val) print(ans)
Java
import java.util.*;
public class Main {
static final int N = 100010;
static class Node {
int a, b, c;
}
// 每个值的剩余数量
static int[] cnt = new int[N];
// 一个数的初始数量是否大于 1
static boolean[] gt1 = new boolean[N];
static int n;
public static void main(String[] args) {
long ans = 0;
TreeMap<Integer, Integer> S = new TreeMap<>();
Scanner scan = new Scanner(System.in);
n = scan.nextInt();
Node[] q = new Node[n + 1];
for (int i = 1; i <= n; ++i) {
q[i] = new Node();
q[i].a = scan.nextInt();
// 如果一个值 q[i].a 的总数量大于 1,则 gt1[q[i].a] = true
if (++cnt[q[i].a] > 1) {
gt1[q[i].a] = true;
}
S.put(q[i].a, S.getOrDefault(q[i].a, 0) + 1);
}
for (int i = 1; i <= n; ++i) q[i].b = scan.nextInt();
for (int i = 1; i <= n; ++i) {
q[i].c = scan.nextInt();
ans += (long) q[i].c * q[i].b;
}
// 按照每个空间的单位价值从大到小排序,先处理单位价值更大的
Arrays.sort(q, 1, n + 1, new Comparator<Node>() {
@Override
public int compare(Node o1, Node o2) {
return o2.c - o1.c;
}
});
for (int i = 1; i <= n; ++i) {
// 找到小于等于 q[i].b 的最大
Integer key = S.floorKey(q[i].b);
if (key == null) continue;
// 如果选择的物品的体积小于当前物品的空间
// 或者选择的物品的体积等于当前物品的空间,但是这个空间的总数量大于 1
if (!key.equals(q[i].a) || gt1[key]) {
ans -= (long) key * q[i].c;
if (S.get(key) == 1) {
S.remove(key);
} else {
S.put(key, S.get(key) - 1);
}
} else {
// 否则就得继续往前找体积小于当前物品的空间的物品
key = S.floorKey(key - 1);
if (key == null) continue;
ans -= (long) key * q[i].c;
if (S.get(key) == 1) {
S.remove(key);
} else {
S.put(key, S.get(key) - 1);
}
}
}
System.out.println(ans);
}
}
Python、Golang 和 JavaScript 标准库中没有实现基于平衡树的数据结构,故如实在需要,还是需要 C++ 和 Java


被折叠的 条评论
为什么被折叠?



