1. 程式人生 > >學習筆記第二十五節:Meet in the Middle

學習筆記第二十五節:Meet in the Middle

正題

      Meet in the Middle,折半搜尋。

      用來解決一些普通搜尋過不了的,但是支援合併的題目。

      以這一題為例:[CEOI2015 Day2]世界冰球錦標賽

      這題說的是什麼呢。

      集合滿足元素之和小於m,求集合種數。

      n才40,讓我們來暴力。

      發現不行,因為列舉種數為2^{40}

      我們先把它拆成兩部分?

       隨便分成兩部分,[1,mid],[mid+1,n]

       兩邊分別進行搜尋,輸出答案,很明顯是錯的。

      因為沒有計算到答案同時在兩邊的影響,怎麼計算?

      我們可以把左邊的答案處理出來,也就是說,對於左邊可能產生的權值,我們都把他記錄下來。

      右邊也一樣。

      然後我們再對左邊的答案排個序,那麼對於右邊其中的a來說,它可能產生的集合是與左邊<=m-a的狀態相結合。

      最後輸出答案即可。

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;

int n;
long long m;
long long s[50];
long long a[1050000],b[1050000];

void dfs(int x,int y,long long t,long long *now){
    if(t>m) return ;
    if(x==y+1){
        now[++now[0]]=t;
        return;
    }
    dfs(x+1,y,t+s[x],now);
    dfs(x+1,y,t,now);
}

long long find_last(long long x){
    int l=1,r=a[0];
    int ans=0;
    while(l<=r){
        int mid=(l+r)/2;
        if(a[mid]<=x){
            ans=mid;
            l=mid+1;
        }
        else r=mid-1;
    }
    return ans;
}

int main(){
    scanf("%d %lld",&n,&m);
    for(int i=1;i<=n;i++) scanf("%lld",&s[i]);
    int mid=(1+n)/2;
    dfs(1,mid,0,a);
    dfs(mid+1,n,0,b);
    sort(a+1,a+1+a[0]);
    long long ans=0;
    for(int i=1;i<=b[0];i++) ans+=find_last(m-b[i]);
    printf("%lld",ans);
}