[R64F] 购买
- 难度 普及+/提高
- 时限 2s
- 空限 512m
- 线段树
数据规模:,,。
思路
直接模拟每个人从 走到 是 ,会超时。
观察每个人的购买过程:他只会买「当前手里钱买得起的、还没被买走的最靠左商品」。这是正确的,因为他的钱只会减少不会增加——当全局最左的可买商品是 时, 左侧所有未售商品价格都大于当前钱数,而更早时刻他的钱更多时都买不起,之后更买不起;已售商品也不可能再买。因此反复找到这个 并买下,就等价于他从左到右的真实购物过程。
用一棵线段树维护:
- 叶子:若商品 尚未售出,值为价格 ;已售出则为 ;
- 内部节点:区间内剩余商品价格的最小值。
对每个人,反复执行:
- 查询全局第一个价格不超过当前钱数的位置 :从根开始,若左儿子最小值不超过当前钱数则往左走,否则往右走,直到叶子(线段树上二分,)。若根最小值已大于当前钱数,说明买不起了,此人购物结束;
- 买下 :扣钱,把叶子 单点更新为 。
由于每次成功购买都会让一件商品永久下架,所有人的购买次数总和不超过 ;每个人还会做一次失败的查询来结束购物,所以线段树操作总次数为 。
复杂度
要大于任何可能的金额( 量级),取 即可。
时间复杂度 ,空间复杂度 。
仓颉实现
import std.env.*
import std.convert.*
var INF: Int64 = 1000000000000000000
var tree = Array<Int64>(0, { _ => 0 })
func build(node: Int64, l: Int64, r: Int64, a: Array<Int64>): Unit {
if (l == r) {
tree[node] = a[l]
return
}
let mid = (l + r) / 2
build(node * 2, l, mid, a)
build(node * 2 + 1, mid + 1, r, a)
if (tree[node * 2] < tree[node * 2 + 1]) {
tree[node] = tree[node * 2]
} else {
tree[node] = tree[node * 2 + 1]
}
}
func update(node: Int64, l: Int64, r: Int64, pos: Int64): Unit {
if (l == r) {
tree[node] = INF
return
}
let mid = (l + r) / 2
if (pos <= mid) {
update(node * 2, l, mid, pos)
} else {
update(node * 2 + 1, mid + 1, r, pos)
}
if (tree[node * 2] < tree[node * 2 + 1]) {
tree[node] = tree[node * 2]
} else {
tree[node] = tree[node * 2 + 1]
}
}
func findFirst(node: Int64, l: Int64, r: Int64, money: Int64): Int64 {
if (tree[node] > money) {
return -1
}
if (l == r) {
return l
}
let mid = (l + r) / 2
let res = findFirst(node * 2, l, mid, money)
if (res != -1) {
return res
}
return findFirst(node * 2 + 1, mid + 1, r, money)
}
main(): Int64 {
let reader = getStdIn()
let line0 = reader.readln().getOrThrow().split(" ", removeEmpty: true).map({ p: String => Int64.parse(p) })
let nn = line0[0]
let mm = line0[1]
let av = reader.readln().getOrThrow().split(" ", removeEmpty: true).map({ p: String => Int64.parse(p) })
let xs = reader.readln().getOrThrow().split(" ", removeEmpty: true).map({ p: String => Int64.parse(p) })
var a = Array<Int64>(nn + 1, { _ => 0 })
for (i in 1..=nn) {
a[i] = av[i - 1]
}
tree = Array<Int64>(4 * nn + 5, { _ => 0 })
build(1, 1, nn, a)
var sb = StringBuilder()
for (j in 0..mm) {
var money = xs[j]
var line = StringBuilder()
var cnt: Int64 = 0
while (true) {
let p = findFirst(1, 1, nn, money)
if (p == -1) {
break
}
if (cnt > 0) {
line.append(" ")
}
line.append(p.toString())
cnt += 1
money -= a[p]
update(1, 1, nn, p)
}
sb.append(cnt.toString())
if (cnt > 0) {
sb.append(" ")
sb.append(line.toString())
}
sb.append("\n")
}
print(sb.toString())
return 0
}
要点:
- 金额与价格之差最多到 量级,必须用
Int64; 取 。 - 每人的购买编号天然按从小到大输出(每次取的都是全局最左可买位置)。
- 输出量大,每行用
StringBuilder拼接,最后一次性输出。