折半搜索(meet in the middle)。
首先考虑正常的搜索,时间复杂度 O ( 2 40 ) O(2^{40}) O(240)。
所以考虑折半搜索,从两边对着搜,具体就是搜索 1 ∼ n 2 1\sim\dfrac{n}{2} 1∼2n、 n 2 + 1 ∼ n \dfrac{n}{2}+1\sim n 2n+1∼n 两个区间内的最优解。这样优化之后,时间复杂度就变为 O ( 2 n 2 + 1 ) O(2^{\frac{n}{2}+1}) O(22n+1)。
第一遍搜索前半部分时,我们的限制是不超过
M
M
M。可以记录此时对于一个合法方案,它的消耗钱数 sum
是多少。
到了第二遍搜索后半部分时,需要知道的是,我们可以统计的方案是前后加一起小于
M
M
M 的方案。于是乎我们先对第一次得到的方案进行 sort
,枚举选后一半的所有情况,如果和前一半合并之后总钱数仍小于
M
M
M,可以把这些可能全部累计到 ans
中。由于前一半的方案已经被我们排成有序的了,所以我们可以用 upper_bound
函数快速查找出可以统计的情况。
具体代码实现:
#include <bits/stdc++.h>
using namespace std;
#define int long long
// 对于此题的两次 DFS 来说,要统计 1 个 sum(当前消耗钱数)和 pos(已经搜到哪里了)
const int maxn=1<<25;//左移 25 位相当于 2 的 25 次方
int q1[maxn],q2[maxn],co[45],ans,N,M,cnt1,cnt2,mid;
void dfs1(int sum,int pos)
{
if(sum>M) return;
if(pos>mid) {q1[++cnt1]=sum;return;}
dfs1(sum+co[pos],pos+1);
dfs1(sum,pos+1);//考虑不选当前位,继续向下搜
}
void dfs2(int sum,int pos)
{
if(sum>M) return;
if(pos>N) {q2[++cnt2]=sum;return;}
dfs2(sum+co[pos],pos+1);
dfs2(sum,pos+1);
}
signed main()
{
cin>>N>>M;
mid=(N+1)/2;
for(int i=1;i<=N;i++) cin>>co[i];
dfs1(0,1);
sort(q1+1,q1+cnt1+1);
dfs2(0,mid+1);
for(int i=1;i<=cnt2;i++)
ans+=upper_bound(q1+1,q1+cnt1+1,M-q2[i])-q1-1;
// cout<<cnt<<endl;
// cout<<N/2<<endl;
cout<<ans;
return 0;
}