子集
子集
【问题描述】
R君得到了⼀个集合,⾥⾯⼀共有n个正整数。
R君对这个集合很感兴趣。R 君通过努⼒钻研,发现了这个集合⼀共有\(2^n\)个子集。
现在R君又对这个集合的⼦集很感兴趣。
定义⼀个集合的权值是这个集合内所有数字的和的话。
那么R君想问问你,这个集合的权值第K小子集是多⼤。
ps. 涉及到较少数字的 long long 输⼊输出,建议使用 cin/cout。
【输入格式】
第⼀⾏两个正整数 n,k。 接下来一行 n 个正整数,表⽰集合内元素。
【输出格式】
输出⼀个数字,表⽰集合的权值第 K 小子集的权值。
【样例输入】
2 3 1 2
【样例输出】
2
6
【数据规模及约定】
对于前 20% 的数据,1 ≤ n ≤ 15。
对于前 40% 的数据,1 ≤ n ≤ 22。
对于前 100% 的数据,1 ≤ n ≤ 35, 1 ≤ k ≤ 2n,1 ≤ 集合元素 ≤ 109。
我们考虑枚举集合中的子集,看到数据范围比较小,我们可以考虑状态压缩(其实一般范围比较小的子集或者选与不选的问题都可以用状态压缩的二进制来表示)。
然后因为两个子集取的集合合并起来必定包含原集合的所有子集,所以我们可以考虑二分,这样复杂度可以大大降低。
这样我们可以考虑两个指针,分别有一个在均分之后的集合里qwq,然后排完序之后指针的位置(答案)就拥有了单调性。
这样我们就可以遍历出位置了。
可能我说的不是很清楚,但是这种做法是有算法名称的——meet in the middle或者two pointer(就当个trick记下来吧)
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#define LL long long
using namespace std;
LL n,k;
int n1,n2;
LL a[105];
LL b1[(1<<18)+5];
LL b2[(1<<18)+5];
int lowbit(int x){
return x&-x;
}
void prepare(LL a[],LL b[],LL n)
{
for(int i=0;i<n;i++) b[1<<i] = a[i];
for(int i=1;i<(1<<n);i++){
b[i]=b[i^lowbit(i)]+b[lowbit(i)];
}
}
bool judge(LL x){
int p1=0,p2=0;
while(p2+1<(1<<n2) && b1[p1]+b2[p2+1]<=x) p2++;
LL cnt = 0;
while(p1<(1<<n1))
{
while(p2>=0&&b1[p1]+b2[p2]>x) p2--;
if(p2<0) break;
cnt+=p2+1;
p1++;
}
return cnt>=k;
}
int main(){
//freopen("subset.in","r",stdin);
//freopen("subset.out","w",stdout);
cin >> n>>k;
for(int i=0;i<n;i++) cin>>a[i];
n1= n/2,n2 = n-n1;
prepare(a,b1,n1);
prepare(a+n1,b2,n2);
sort(b1,b1+(1<<n1));
sort(b2,b2+(1<<n2));
LL l=-1,r=35*1LL*(int)1e9;
while(r-l>1)
{
LL mid=(l+r)/2;
if(judge(mid)) r=mid;
else l=mid;
}
cout<<r<<endl;
return 0;
}