[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;
}

猜你喜欢

转载自www.cnblogs.com/Chtholly/p/11804901.html