[CSP校内集训]building(折半搜索)

题意

(n)个东西,每个可以选择不取、取价值(a_i)、取价值(b_i),求恰好凑出价值(x)的方案数(,(nleq 25,a_i,b_i,xleq 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;
}
原文地址:https://www.cnblogs.com/Chtholly/p/11804901.html