1. 程式人生 > >子集(狀態壓縮)(meet in the middle)

子集(狀態壓縮)(meet in the middle)

cout 枚舉集合 格式 long long void sub The oid namespace

子集

【問題描述】

R 君得到了?個集合,???共有 n 個正整數。
R 君對這個集合很感興趣。 R 君通過努?鉆研,發現了這個集合?共有 2n 個子集。
現在 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;
}

子集(狀態壓縮)(meet in the middle)