题目描述
译自 CEOI2015 Day2 T1「Ice Hockey World Championship」
今年的世界冰球锦标赛在捷克举行。Bobek 已经抵达布拉格,他不是任何团队的粉丝,也没有时间观念。他只是单纯的想去看几场比赛。如果他有足够的钱,他会去看所有的比赛。不幸的是,他的财产十分有限,他决定把所有财产都用来买门票。
给出 Bobek 的预算和每场比赛的票价,试求:如果总票价不超过预算,他有多少种观赛方案。如果存在以其中一种方案观看某场比赛而另一种方案不观看,则认为这两种方案不同。
输入格式
第一行,两个正整数 N N N 和 M ( 1 ≤ N ≤ 40 , 1 ≤ M ≤ 1 0 18 ) M(1 \leq N \leq 40,1 \leq M \leq 10^{18}) M(1≤N≤40,1≤M≤1018),表示比赛的个数和 Bobek 那家徒四壁的财产。
第二行, N N N 个以空格分隔的正整数,均不超过 1 0 16 10^{16} 1016,代表每场比赛门票的价格。
输出格式
输出一行,表示方案的个数。由于 N N N 十分大,注意:答案 ≤ 2 40 \le 2^{40} ≤240。
样例 #1
样例输入 #1
5 1000
100 1500 500 500 1000
样例输出 #1
8
提示
样例解释
八种方案分别是:
- 一场都不看,溜了溜了
- 价格 100 100 100 的比赛
- 第一场价格 500 500 500 的比赛
- 第二场价格 500 500 500 的比赛
- 价格 100 100 100 的比赛和第一场价格 500 500 500 的比赛
- 价格 100 100 100 的比赛和第二场价格 500 500 500 的比赛
- 两场价格 500 500 500 的比赛
- 价格 1000 1000 1000 的比赛
有十组数据,每通过一组数据你可以获得 10 分。各组数据的数据范围如下表所示:
数据组号 | 1 − 2 1-2 1−2 | 3 − 4 3-4 3−4 | 5 − 7 5-7 5−7 | 8 − 10 8-10 8−10 |
---|---|---|---|---|
N ≤ N \leq N≤ | 10 10 10 | 20 20 20 | 40 40 40 | 40 40 40 |
M ≤ M \leq M≤ | 1 0 6 10^6 106 | 1 0 18 10^{18} 1018 | 1 0 6 10^6 106 | 1 0 18 10^{18} 1018 |
折半搜索 Meet in the middle - AC
价格太大,难以dp.
n比较小,但如果搜索,
O
(
2
n
)
O(2^n)
O(2n)还是顶不住。而且答案不超过
2
40
2^{40}
240,肯定不是一个一个数。
不过,如果n变成一半,
2
20
≈
1
0
6
2^{20}\approx 10^6
220≈106,很好的。
算法应运而生:把所有比赛分成两半,搜出只看前一半里面的比赛的所有可能花费,以及只看后一半的。但答案不是简单的相加,有可能看的比赛既有前一半的,又有后一半的。容易想到,枚举在第一半的花费,得到在第二半的最多花费,二分查找求出方案数,时间复杂度
O
(
t
l
o
g
t
)
O(tlogt)
O(tlogt),其中
t
=
2
n
/
2
t=2^{n/2}
t=2n/2.
事实上,这就是折半搜索(Meet in the middle)。一般的搜索时间复杂度是指数级
O
(
a
n
)
O(a^n)
O(an),折半搜索是
O
(
a
n
/
2
)
O(a^{n/2})
O(an/2),适用于n比较小,三四十这样,但暴搜仍然不够的情况。
把搜索过程分成两半,分别搜索,最后合并。如何合并是需要重点考虑的部分。
int n;
ll m, a[MAXN], ans;
vector<ll> v[2];
void dfs(int u, int r, ll s, int t) {
if (u > r) {
v[t].push_back(s);
return;
}
dfs(u + 1, r, s + a[u], t);
dfs(u + 1, r, s, t);
}
int main() {
scanf("%d%lld", &n, &m);
for (int i = 1; i <= n; ++i) {
scanf("%lld", a + i);
}
int mid = (1 + n) >> 1;
dfs(1, mid, 0, 0);
dfs(mid + 1, n, 0, 1);
sort(v[0].begin(), v[0].end());
sort(v[1].begin(), v[1].end());
for (ll i : v[0]) {
if (i > m) break;
ans += upper_bound(v[1].begin(), v[1].end(), m - i) - v[1].begin();
}
printf("%lld\n", ans);
return 0;
}