1. 程式人生 > >【bzoj4800】[Ceoi2015]Ice Hockey World Championship 折半搜索

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

ros 一個數 pan 個數 microsoft 價格 ons cpp soft

題目描述

有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 折半搜索