折半生成两组子集和,排序一边并用 bisect_right 统计预算内组合。
OJ: luogu
题目 ID: P4799
难度:提高+/省选-
标签:Meet-in-the-Middle子集和二分python
日期: 2026-07-16 20:10
题意
至多 40 场比赛,每场可选或不选,统计总票价不超过预算的方案数,包括空集。
思路
直接枚举 x,右边可以选择所有不超过 budget-x 的子集和。
把右和排序后,bisect_right(right, budget-x) 的返回下标正好是合法右方案数,累加即可。相同价格的不同比赛产生不同枚举分支,列表中的重复和不能去重。
Python 知识
sums += [total + value for total in sums ...]由旧子集和批量生成“选择当前项”的新和。bisect_right直接统计小于等于上界的元素个数。sum(generator)流式累加全部左半查询结果。
代码
python
import sys
from bisect import bisect_right
n, budget = map(int, sys.stdin.buffer.readline().split())
prices = list(map(int, sys.stdin.buffer.readline().split()))
def subset_sums(values):
sums = [0]
for value in values:
sums += [total + value for total in sums if total + value <= budget]
return sums
middle = n // 2
left = subset_sums(prices[:middle])
right = sorted(subset_sums(prices[middle:]))
print(sum(bisect_right(right, budget - total) for total in left))复杂度
时间
总结
40 是子集枚举的典型折半信号;一边排序后,二边组合计数就变成普通上界查询。