>Link
luogu P4799
>Description
给出预算 M M M和 N N N场比赛的票价,试求:如果总票价不超过预算,有多少种观赛方案。
如果存在以其中一种方案观看某场比赛而另一种方案不观看,则认为这两种方案不同。
1 ≤ N ≤ 40 , 1 ≤ M ≤ 1 0 18 , a i ≤ 1 0 16 1\le N \le 40,1\le M \le 10^{18},a_i \le 10 ^{16} 1≤N≤40,1≤M≤1018,ai≤1016
>解题思路
部分分 N ≤ 20 N\le 20 N≤20:很容易想到搜索,当前的比赛有选或不选两种选择
但是 N ≤ 40 N\le 40 N≤40, 2 40 2^{40} 240很明显会爆
所以这里引进 折半搜索 (meet in the middle),顾名思义就是在中间相遇
考虑一种问题,问从 1 1 1 到 N N N 的路径有多少条,爆搜会T 并且会进行一些无意义的搜索,就是搜到一条路,这条路往下走一定不可能到达 N N N
我们把搜索的层从中间分隔开,先从 1 1 1 搜到中间某点 A A A,从 N N N 搜到中间某点 B B B,如果 A A A 可以走到 B B B,那就有一条路径 1 > > A > > B > > N 1>>A>>B>>N 1>>A>>B>>N。前提是路径都是无向的
这样时间复杂度从 O ( 2 n ) O(2^n) O(2n) 变成了 O ( 2 n / 2 ) O(2^{n/2}) O(2n/2)
把 N N N 场比赛分成两部分,按照上面的思路,如果第一条“路径”的花费加上第二条“路径”的花费不超过预算,说明可行
我是直接把第一次搜索得到的结果存进数组,排序,第二次搜索时二分找对应的符合条件的第一次搜索的方案有多少个
>代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <map>
#define LL long long
#define N 50
using namespace std;
int n, tot, T1, T2;
LL ans, m, a[N], q[2000000];
void dfs1 (int now, LL sum)
{
if (sum > m) return;
if (now > T1)
{
q[++tot] = sum;
return;
}
dfs1 (now + 1, sum + a[now]);
dfs1 (now + 1, sum);
}
void dfs2 (int now, LL sum)
{
if (sum > m) return;
if (now < T2)
{
ans += upper_bound (q + 1, q + 1 + tot, m - sum) - q - 1;
return;
}
dfs2 (now - 1, sum + a[now]);
dfs2 (now - 1, sum);
}
int main()
{
scanf ("%d%lld", &n, &m);
for (int i = 1; i <= n; i++) scanf ("%lld", &a[i]);
T1 = n / 2 + (n & 1);
T2 = T1 + 1;
dfs1 (1, 0);
sort (q + 1, q + 1 + tot);
dfs2 (n, 0);
printf ("%lld", ans);
return 0;
}