D Knapsack Cryptosystem 2019牛客暑假多校第九场 (折半枚举)

链接:D.Knapsack Cryptosystem
题意:给你一个长为n的序列,以及一个数s
你要从序列中选择一些数字,使他们的和为m(满足组合唯一)
并最后以01串的格式输出(0表示没有取,1表示取)
思路:一开始题中给了一个 (背包加密算法的wiki) 然而跟这道题没有关系..
此题数据范围值得注意:(1 <= n <= 36)          (0<= s< 9 e18)        (ai  < 2 e17)
如果我们想要去暴搜,或者普通的状压dp,估计一个TLE,一个RE
所以我们可以尝试折半状压去枚举,这里我们就开map去映射一下对应状态的和为多少


#include<bits/stdc++.h> #define ll long long using namespace std; const int maxn = 40; map<ll,ll>G; ll arr[maxn],s; int n; int main(){ cin>>n>>s; { for(int i=0;i<n;i++){ cin>>arr[i]; } int mid = n/2; ll Max = 1<<(n/2); //2e18的状态 for(int i=0;i<Max;i++){ ll sum = 0; for(int j=0;j<mid;j++){ //从右往左数,取第j+1位上的值 if((i>>j)&1) sum += arr[j]; } G[sum] = i; } ll MMax = 1<<((n+1)/2); for(int i=0;i<MMax;i++){ ll sum = 0; for(int j=0;j<((n+1)/2);j++){ if(i>>j&1) sum += arr[mid+j]; } if(G.count(s-sum)){ ll k = G[s-sum]; int cnt = 0;//记录前导零个数 while(k){ //末位为1 cnt++; if(k&1) cout<<1; else cout<<0; k >>= 1; } while(cnt<mid) { cnt++; cout<<0; } cnt = 0; while(i){ cnt ++; if(i&1) cout<<1; else cout<<0; i >>= 1; } while(cnt<(n+1)/2) {cout<<0; cnt++;} return 0; } } } }

猜你喜欢

转载自www.cnblogs.com/Tianwell/p/11372748.html