【BZOJ】2679 [Usaco2012 Open]Balanced Cow Subsets 折半搜索+状压

题目传送门

第一眼看到这题,以为可以直接大力搜索,T了一发以后发现时间复杂度为 O(320) ,于是就想折半搜索。

然而直接折半搜索WA了一发以后我停下了盲目的敲代码,仔细思考了题目。造了一组数据:“6 88 36 36 7 95 24”,发现{88,36,7}与{36,95}有两种组合情况,但是答案只会被加一次。

于是想到状压,判断两个相等的集合内取了哪些数,开个bool数组去一下重即可。

附上AC代码:

# pragma GCC optimize "O3"
# pragma G++ optimize "O3"
#include <cstdio>
#include <algorithm>
#include <map>
#include <vector>
using namespace std;

map <int,vector <int> > f;
int n,a[21],ans;
bool b[1048580];

inline void so1(int l,int r,int sum,int s){
    if (l>r){
        f[sum].push_back(s);
        return;
    }
    so1(l+1,r,sum,s);
    so1(l+1,r,sum+a[l],s+(1<<l-1));
    so1(l+1,r,sum-a[l],s+(1<<l-1));
    return;
}

inline void so2(int l,int r,int sum,int s){
    if (l>r){
        for (int i=0; i<f[-sum].size(); ++i)
            if (!b[s|f[-sum][i]]) b[s|f[-sum][i]]=1,++ans;
        return;
    }
    so2(l+1,r,sum,s);
    so2(l+1,r,sum+a[l],s+(1<<l-1));
    so2(l+1,r,sum-a[l],s+(1<<l-1));
    return;
}


int main(void){
    scanf("%d",&n);
    for (int i=1; i<=n; ++i) scanf("%d",&a[i]);
    so1(1,n>>1,0,0),so2((n>>1)+1,n,0,0),printf("%d\n",ans-1);
    return 0;
}

但是!!!

上述代码可能TLE,实测BZOJ数据跑出来需要10576ms,卡时限AC……QAQ

于是想到计算答案的那个搜索调用STL次数太多,需要用一些常数较小的数据结构来优化一下。由map想到了手打hash map。

于是orz zzk,大佬教我打hash map,太强啦。

附上AC代码:

#include <cstdio>
#include <vector>
using namespace std;

typedef long long ll;
const int m=0x7fffffff,mod=999917;
int n,a[21],ans;
int num,h[mod],nt[59050];
vector <int> f[59050];
ll w[59050];
bool b[1<<20];

inline void ist(ll sum,int s){
    for (int i=h[(sum+=m)%mod]; i; i=nt[i]) if (w[i]==sum) return f[i].push_back(s);
    return (void)(w[++num]=sum,f[num].push_back(s),nt[num]=h[sum%mod],h[sum%mod]=num);
}
inline int find(ll sum){
    for (int i=h[(sum+=m)%mod]; i; i=nt[i]) if (w[i]==sum) return i;
    return 0;
}

inline void so1(int l,int r,int sum=0,int s=0){
    if (l>r) return ist(sum,s);
    return so1(l+1,r,sum,s),so1(l+1,r,sum+a[l],s+(1<<l-1)),so1(l+1,r,sum-a[l],s+(1<<l-1));
}

inline void so2(int l,int r,int sum=0,int s=0){
    if (l>r){
        for (int i=find(-sum),j=0; j<f[i].size(); ++j)
            if (!b[s|f[i][j]]) b[s|f[i][j]]=1,++ans;
        return;
    }
    return so2(l+1,r,sum,s),so2(l+1,r,sum+a[l],s+(1<<l-1)),so2(l+1,r,sum-a[l],s+(1<<l-1));
}

int main(void){
    scanf("%d",&n);
    for (int i=1; i<=n; ++i) scanf("%d",&a[i]);
    return so1(1,n>>1),so2((n>>1)+1,n),printf("%d\n",ans-1),0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值