[CSP校内集训]building(折半搜索)
题意
有\(n\)个东西,每个可以选择不取、取价值\(a_i\)、取价值\(b_i\),求恰好凑出价值\(x\)的方案数\(,(n\leq 25,a_i,b_i,x\leq 10^{13})\)
思路
这种看起来很简单暴力的题只用考虑爆搜就完了,但是直接爆搜是\(O(3^n)\)会飞
折半搜索,分成将\(n\)分成两部分,前一半搜出来的方案数开个map存起来,后一半搜出来的方案数乘上前面即可,复杂度优化至\(O(3^{n/2}logx)\)
这样做相当于将原来一个个加答案变成一次加一堆
Code
#include<bits/stdc++.h>
#define N 26
#define lowbit(x) ((x)&(-(x)))
#define Min(x,y) ((x)<(y)?(x):(y))
#define Max(x,y) ((x)>(y)?(x):(y))
using namespace std;
typedef long long ll;
int n,half;
ll x,a[N],b[N],ans;
map<ll,ll> mp;//状态的出现次数
template <class T>
void read(T &x)
{
char c; int sign=1;
while((c=getchar())>'9'||c<'0') if(c=='-') sign=-1; x=c-48;
while((c=getchar())>='0'&&c<='9') x=(x<<1)+(x<<3)+c-48; x*=sign;
}
void DFS(int step,ll now)
{
if(step==half+1) {++mp[now];return;}
if(now>x) return;
DFS(step+1,now);
DFS(step+1,now+a[step]);
DFS(step+1,now+b[step]);
}
void dfs(int step,ll now)
{
if(step==n+1)
{
if(now<=x) ans+=mp[x-now];
return;
}
if(now>x) return;
dfs(step+1,now);
dfs(step+1,now+a[step]);
dfs(step+1,now+b[step]);
}
int main()
{
freopen("building.in","r",stdin);
freopen("building.out","w",stdout);
read(n);read(x);
for(int i=1;i<=n;++i)
{
read(a[i]);
ll c=a[i],cnt=0;
while(c)
{
++cnt;
c-=lowbit(c);
}
b[i]=(1LL<<cnt);
}
if(n==1)
{
cout<<(0==x)+(a[1]==x)+(b[1]==x)<<endl;
return 0;
}
half=n/2;
DFS(1,0);
dfs(half+1,0);
cout<<ans<<endl;
return 0;
}