洛谷 P4799 [CEOI2015 Day2]世界冰球锦标赛
Description
Solution
折半搜索好题。
发现到 \(n \leq 2 ^ {20}\) 时,可以直接爆搜出答案,而数据范围是 \(n \leq 2 ^ {40}\),所以我们考虑折半搜索。
具体来说,把前 20 场和后 20 场分别爆搜出答案。
如何爆搜呢?既然是要爆搜,那么爆搜就完了,枚举到一场比赛时,看或者不看都搜索一遍即可(可以看下方代码理解一下)。
搜索完之后如何合并呢?
这个也很简单,我们假设前 20 场存到了 \(a\) 数组中,后 20 场存到了 \(b\) 数组中。
我们对 \(b\) 数组从小到大排序。枚举 \(a\) 数组,然后在 \(b\) 数组中二分答案,查找有多少汾河条件的观看方式,累加即可。
时间复杂度 \(O(2^{\frac{n}{2}} + nlog_n)\)。
Code
#include <iostream>
#include <cstdio>
#include <algorithm>
#define ll long long
using namespace std;
ll n, m, cnta, cntb;
ll t[50], a[1 << 21], b[1 << 21];
inline void dfs(ll l, ll r, ll sum, ll a[], ll &cnt){
if(sum > m) return;
if(l > r){
a[++cnt] = sum;
return;
}
dfs(l + 1, r, sum + t[l], a, cnt);
dfs(l + 1, r, sum, a, cnt);
}
signed main(){
scanf("%lld%lld", &n, &m);
for(ll i = 1; i <= n; i++)
scanf("%lld", &t[i]);
ll mid = n >> 1;
dfs(1, mid, 0, a, cnta);
dfs(mid + 1, n, 0, b, cntb);
sort(a + 1, a + 1 + cnta);
sort(b + 1, b + 1 + cntb);
ll ans = 0;
for(ll i = 1; i <= cnta; i++)
ans += upper_bound(b + 1, b + 1 + cntb, m - a[i]) - b - 1;
printf("%lld\n", ans);
return 0;
}