世界冰球锦标赛[折半搜索]

特别鸣谢

由孟神纠正出的一个错别字,现已更正。
特别提示:膜孟神能有特殊BUFF加成。

题解中涉及大佬的博客

梁神
园神

题目描述

译自 CEOI2015 Day2 T1「Ice Hockey World Championship」
今年的世界冰球锦标赛在捷克举行。Bobek 已经抵达布拉格,他不是任何团队的粉丝,也没有时间观念。他只是单纯的想去看几场比赛。如果他有足够的钱,他会去看所有的比赛。不幸的是,他的财产十分有限,他决定把所有财产都用来买门票。
给出 Bobek 的预算和每场比赛的票价,试求:如果总票价不超过预算,他有多少种观赛方案。如果存在以其中一种方案观看某场比赛而另一种方案不观看,则认为这两种方案不同。

输入格式

第一行,两个正整数 N 和 M ( 1 ≤ N ≤ 40 , 1 ≤ M ≤ 1 0 18 ) M(1 \leq N \leq 40,1 \leq M \leq 10^{18}) M(1N40,1M1018),表示比赛的个数和 Bobek 那家徒四壁的财产。

第二行,N 个以空格分隔的正整数,均不超过 1 0 16 10^{16} 1016 ,代表每场比赛门票的价格。

输出格式

输出一行,表示方案的个数。由于 NN 十分大,注意:答案 ≤ 2 40 \le 2^{40} 240

输入输出样例

输入

5 1000
100 1500 500 500 1000

输出

8

说明/提示

样例解释
八种方案分别是:

一场都不看,溜了溜了
价格100的比赛
第一场价格500的比赛
第二场价格500的比赛
价格 100 的比赛和第一场价格 500的比赛
价格 100 的比赛和第二场价格 500的比赛
两场价格 500的比赛
价格 1000 的比赛
有十组数据,每通过一组数据你可以获得 10 分。各组数据的数据范围如下表所示:

数据组号 1-21−2 3-43−4 5-75−7 8-108−10
N ≤ N \leq N 10 | 20 | 40 |40
M ≤ 1 0 6 ∣ 1 0 18 ∣ 1 0 6 ∣ 1 0 18 M \leq10^6|10^{18} | 10^6|10^{18} M10610181061018

这题在不是很相信洛谷上是紫题,毕竟很快就想出来了。
当然还是得益于大佬提点我的那句:爆搜。
机房有一大佬,一日与不知何处的小学弟QQ上交流,学弟给出一题:现有一背包,背包有一承重W
n个物品,物品有重量 v i v_i vi,求装背包的方案数 。
N ≤ 40 , M ≤ 1 0 18 N\leq40, M\leq10^{18} N40,M1018
对,就是这道题改了下描述。
当日,大佬说题难度普及,要我与其同思其解,帮学弟解疑答惑。
我信了题目的鬼,深以为是背包dp,思良久不得解。
忽另一大佬路过,听其题意后,戏笑而说:爆搜
吾思之,N若为20,尚可枚举,今有40,不可枚举。
忽灵光一闪至,为何不从两边搜,一边搜20,复杂度岂不可过
搜两次,统计方法答案为何?
可若第二次搜完时枚举第一次状态判断合法否,复杂度仍为 2 40 2^{40} 240,吾休矣。
这时大佬突然对我施以援手:记录第一次搜的状态,排序,二分查找
感激甚,大佬无愧于大佬,救人于水深火热之中,如菩萨降临,带众生脱离苦海。

好,
第一次搜时用a数组存下合法状态,即每次搜完后合法的门票价钱和。
对存下的状态进行递增排序
第二次时,对于每一个搜完的合法状态,我们记选择的门票为 b b b;前一次搜出的状态为 a i a_i ai
那么和并状态时,合并合法的条件是 a i + b ≤ m a_i + b \leq m ai+bm 变一下就是 a i ≤ m − b a_i \leq m - b aimb
我们刚刚对a数组进行了排序,那么我们就可以找到第一个大于 m − b m-b mb的数,这个数前面的数都是合法的,
那么合法的就有改数下标-1个,直接加到答案中去即可。
这样的话我们的复杂度就是 O ( 2 n 2 + 2 n 2 l o g ( 2 n 2 ) ) O(2^{\frac{n}{2}} + 2^{\frac{n}{2}} log(2^{\frac{n}{2}})) O(22n+22nlog(22n))
此题可过矣。
C o d e Code Code

#include

#define ll long long
#define MAXN 500010
#define N 201
#define INF 0x3f3f3f3f
#define gtc() getchar()

using namespace std;

template <class T>
inline void read(T &s){
	s = 0; T w = 1, ch = gtc();
	while(!isdigit(ch)){if(ch == '-') w = -1; ch = gtc();}
	while(isdigit(ch)){s = s * 10 + ch - '0'; ch = gtc();}
	s *= w;
}

template <class T>
inline void write(T x){
    if(x < 0) putchar('-'), x = -x;
	if(x > 9) write(x/10);
    putchar(x % 10 + '0');
}

int n;
ll m;
ll w[MAXN];
ll a[1 << 21], cnt = 0;
int mid;

void dfs(int x, ll ans){//枚举前一半
	if(ans > m) return ;//剪枝
	if(x > mid){
		a[++cnt] = ans;
		return ;
	}
//	printf("%d %d\n", x , ans);
	dfs(x+1, ans);
	dfs(x+1, ans + w[x]);
}

ll ans = 0;
void dfs2(int x, ll num){//枚举后一半
	if(num > m) return ;//剪枝
	if(x > n){//搜完后二分查找
		int tx = upper_bound(a+1, a+cnt+1, m - num) - a - 1;
		ans += tx;//累加答案
		return ;
	}
	
	dfs2(x+1, num);
	dfs2(x+1, num+w[x]);
}
int main()
{
//	freopen(".in", "r", stdin);
//	freopen(".out", "w", stdout);
	read(n), read(m);
	mid = n >> 1;
	for(int i = 1; i <= n; ++i) read(w[i]);
	
	dfs(1, 0);
	sort(a + 1, a + cnt + 1);
	dfs2(mid + 1, 0);
	
	cout << ans << endl;
    return 0;
}


你可能感兴趣的:(搜索,二分)