题目链接: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;
}