【bzoj4800】[Ceoi2015]Ice Hockey World Championship 折半搜索

题目描述

有n个物品,m块钱,给定每个物品的价格,求买物品的方案数。

输入

第一行两个数n,m代表物品数量及钱数
第二行n个数,代表每个物品的价格
n<=40,m<=10^18

输出

一行一个数表示购买的方案数
(想怎么买就怎么买,当然不买也算一种)

样例输入

5 1000
100 1500 500 500 1000

样例输出

8


题解

裸的折半搜索meet-in-the-middle

由于直接爆搜肯定会TLE,考虑把整个序列分成左右两部分,对于每部分求出它所有可以消耗钱数的方案。然后考虑左右组合怎么能够使总钱数不超过m。

考虑枚举右边序列的某钱数,那么左边的钱数要求就是不超过m-右边钱数。所以可以对左边序列排序,然后再二分查找即可知道不超过m-右边钱数的数的个数。把这个个数累加到答案中即可。

时间复杂度 $O(2^{\frac n2}*\log 2^{\frac n2}=2^{\frac n2}*n)$

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
ll a[1 << 21] , b[1 << 21] , w[50] , m;
int ta , tb;
void dfs(int x , int n , ll now , ll *a , int &ta)
{
	if(now > m) return;
	if(x > n)
	{
		a[++ta] = now;
		return;
	}
	dfs(x + 1 , n , now , a , ta) , dfs(x + 1 , n , now + w[x] , a , ta);
}
int main()
{
	int n , i;
	ll ans = 0;
	scanf("%d%lld" , &n , &m);
	for(i = 1 ; i <= n ; i ++ ) scanf("%lld" , &w[i]);
	dfs(1 , n >> 1 , 0 , a , ta);
	dfs((n >> 1) + 1 , n , 0 , b , tb);
	sort(a + 1 , a + ta + 1);
	for(i = 1 ; i <= tb ; i ++ )
	{
		if(m - b[i] >= a[ta]) ans += ta;
		else ans += upper_bound(a + 1 , a + ta + 1 , m - b[i]) - a - 1;
	}
	printf("%lld\n" , ans);
	return 0;
}

 

 

posted @ 2017-07-26 14:57  GXZlegend  阅读(655)  评论(1编辑  收藏  举报