package org.josh;
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt(); // 物品数量
long w = scanner.nextLong(); // 背包容量,使用long防止溢出
int[] v = new int[n]; // 存储每个物品的价值(这里视为重量)
for (int i = 0; i < n; i++) {
v[i] = scanner.nextInt();
}
// 使用分治法将物品分成两组,分别计算子集和int mid = n / 2; // 将物品数组分为前半部分和后半部分List<Long> sumA = generateSubsetSums(v, 0, mid); // 前半部分的所有子集和List<Long> sumB = generateSubsetSums(v, mid, n); // 后半部分的所有子集和Collections.sort(sumB); // 对后半部分的子集和进行排序,便于后续二分查找long count = 0; // 统计有效子集组合的数量for (long a : sumA) { // 遍历前半部分的每个子集和aif (a > w) continue; // 剪枝:如果当前a已经超过背包容量,跳过long remaining = w - a; // 计算剩余可用容量// 在sumB中找到不大于remaining的元素个数,并累加到countint idx = upperBound(sumB, remaining);count += idx;}System.out.println(count); // 输出结果
}/*** 生成指定区间[start, end)内所有子集的和* @param v 物品数组* @param start 起始索引(包含)* @param end 结束索引(不包含)* @return 所有可能子集的和的列表*/
private static List<Long> generateSubsetSums(int[] v, int start, int end) {List<Long> sums = new ArrayList<>();int n = end - start; // 当前区间的元素个数// 遍历所有可能的子集(通过位掩码表示)for (int mask = 0; mask < (1 << n); mask++) {long sum = 0;// 检查每个位是否被选中,计算对应的子集和for (int i = 0; i < n; i++) {if ((mask & (1 << i)) != 0) { // 第i位被选中sum += v[start + i]; // 将对应物品的价值累加}}sums.add(sum);}return sums;
}/*** 在已排序的列表中查找最后一个小于等于target的元素位置,返回符合条件元素的数量* @param list 已排序的列表* @param target 目标值* @return 列表中不大于target的元素个数*/
private static int upperBound(List<Long> list, long target) {int left = 0, right = list.size() - 1;int res = -1; // 初始化为-1,处理所有元素都大于target的情况while (left <= right) {int mid = (left + right) >>> 1; // 无符号右移防止溢出if (list.get(mid) <= target) {res = mid; // 记录当前位置,并继续向右查找可能的更大值left = mid + 1;} else {right = mid - 1; // 中间值过大,向左查找}}// res是最后一个符合条件的元素索引,返回数量为res+1(索引转个数)return res + 1;
}
}