牛客多校第八场D——Knapsack Cryptosystem(枚举+二分)

题目链接:https://ac.nowcoder.com/acm/contest/889/D

题目大意:

给你n个数(n<=36)和一个和s(s<=9*1e18),每个数的大小和s一致,你需要在36个数中选择一些数,这些数的和恰好为s,

请输出选择的这些数,用二进制表示每个数选或不选。

思路:

暴力枚举每个数选或不选肯定是不行的,2^36无法承受;背包也不行,s太大,状态难以承受。

所以此时需要折半搜索,这种思路很经典,之前在01字典树中也有用到,将整个图分成两半进行搜索,再加以验证。

此处,将n个数分为前半段和后半段,枚举前半段的所有情况,复杂度最大为2^18,枚举后半段的所有情况,复杂度为2^18,然后再枚举前半段的所有值v1,在右半段中二分搜索是否有一个值v2,使得v1+v2==sum,这样时间复杂度就可以承受了,O(nlog(n)),n为2^18。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
vector<ll>v1,v2;
const int maxn=40;
ll a[maxn];
map<ll,int>mp1,mp2;
signed main(){
    ll n,s;
    scanf("%lld%lld",&n,&s);
    for(int i=1;i<=n;i++){
        scanf("%lld",&a[i]);
    }
    int k=(n+1)/2;
    for(int i=0;i<(1<<k);i++){
        ll sum=0;
        for(int j=0;j<k;j++){
            if(i&(1<<j)){
                sum+=a[j+1];
            }
        }
        v1.push_back(sum);
         mp1[sum]=i;
    }
    int u=n-k;
    for(int i=0;i<=(1<<u);i++){
        ll sum=0;
        for(int j=0;j<u;j++){
            if(i&(1<<j)){
                sum+=a[j+1+k];
            }
        }
        v2.push_back(sum);
        mp2[sum]=i;
    }
    /*
    for(int i=0;i<v1.size();i++){
        printf("%d ",v1[i]);
    }
    puts("");
    for(int i=0;i<v2.size();i++){
        printf("%d ",v2[i]);
    }
    puts("");
    */
    sort(v1.begin(),v1.end());
    sort(v2.begin(),v2.end());
    v1.erase(unique(v1.begin(),v1.end()),v1.end());
    v2.erase(unique(v2.begin(),v2.end()),v2.end());
    for(int i=0;i<v1.size();i++){
        ll now=s-v1[i];
        int l=0,r=v2.size()-1;
        int ans=-1;
        while(l<=r){
            int mid=(l+r)>>1;
            if(now==v2[mid]){
                ans=mid;
                break;
            }
            if(now>v2[mid]){
                l=mid+1;
            }
            else{
                r=mid-1;
            }
        }
        if(ans==-1)continue;
        else{
            int ans1=mp1[v1[i]];
            int ans2=mp2[v2[ans]];

            for(int j=0;j<k;j++){
                if(ans1&(1<<j)){
                    printf("1");
                }
                else{
                    printf("0");
                }
            }
            for(int j=0;j<u;j++){
                if(ans2&(1<<j)){
                    printf("1");
                }
                else{
                    printf("0");
                }
            }
            puts("");
            return 0;
        }
    }
    return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值