[折半搜索] 购物

题目描述

Y u n o Yuno Yuno 刚刚获得了 X X X 元奖金, 她想用奖金买一些物品。
购物车里面有 N N N 个物品,每个物品的价格是 V i V_i Vi Y u n o Yuno Yuno 想尽可能地把手头的奖金给花光,所以她要精心选择?些商品,使得其价格总和最接近但又不会超过奖金的金额。
Y u n o Yuno Yuno 想知道最少能剩下多少奖金。

输入格式

第一行两个整数 N N N X X X
第二行 N N N 个整数 V i V_i Vi,表示第 i i i 个物品的价格。

输出格式

输出一行整数,表示 Y u n o Yuno Yuno 最后最少可以剩下的钱数。

样例

样例输入1:

4 50
1 2 3 4

样例输出1:

40

样例输入2:

4 5
1 2 3 4

样例输出2:

0

数据范围

对于 20 % 20\% 20% 的数据, 1 ≤ N ≤ 10 1 \le N \le 10 1N10
对于 40 % 40\% 40% 的数据, 1 ≤ N ≤ 20 1 \le N \le 20 1N20 1 ≤ X , V i ≤ 1 0 4 1 \le X, V_i \le 10^4 1X,Vi104
对于 100 % 100\% 100% 的数据, 1 ≤ N ≤ 40 1 \le N \le 40 1N40 1 ≤ X , V i ≤ 1 0 9 1 \le X, V_i \le 10^9 1X,Vi109

题解

对于 40 % 40\% 40% 的数据,我们可以直接使用 背包dfs 做。

对于 100 % 100\% 100% 的数据,由于 1 ≤ N ≤ 40 1 \le N \le 40 1N40 的数据范围,所以很容易想到折半搜索。

先搜前一半,将搜出可能的钱数放进集合。再搜后一半,也搜出一个钱数,从前面的集合中取出一个最大的数保证这个数加上钱数小于等于 X X X,更新答案。

两次搜索复杂度为 O ( 2 20 ) O(2^{20}) O(220),二分复杂度为 O ( 20 ) O(20) O(20),总复杂度为 O ( 20 × 2 21 ) O(20 \times 2^{21}) O(20×221)

#include<bits/stdc++.h>
#define int long long
using namespace std;
int n, m, t;
int a[50];
int len = 0;
int f[1100000];
int ans = 1e16;
void dfs1(int x, int y){
	if(y > m){
		return;
	} 
	if(x > t){
		f[++ len] = y;
		return;
	}
	dfs1(x + 1, y + a[x]);
	dfs1(x + 1, y);
}
void dfs2(int x, int y){
	if(y > m){
		return;
	} 
	if(x > n){
		int tt = upper_bound(f + 1, f + len + 1, m - y) - f - 1;
		if(f[tt] + y <= m){
			ans = min(ans, m - f[tt] - y);
		}
	//	printf("%d %d %d\n", f[tt], y, m - f[tt] - y);
		return;
	}
	dfs2(x + 1, y + a[x]);
	dfs2(x + 1, y);
}
signed main(){
	scanf("%lld %lld", &n, &m);
	t = n / 2;
	for(int i = 1; i <= n; ++ i){
		scanf("%lld", &a[i]);
	}
	dfs1(1, 0);
	sort(f + 1, f + len + 1);
	dfs2(t + 1, 0);
	printf("%lld", ans);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值