首页 > 代码库 > 【bzoj4800】[Ceoi2015]Ice Hockey World Championship 折半搜索

【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^{\fracn2}*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;}

 

 

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