1. 程式人生 > >[CEOI2015 Day2]世界冰球錦標賽 (折半搜尋)

[CEOI2015 Day2]世界冰球錦標賽 (折半搜尋)

[CEOI2015 Day2]世界冰球錦標賽

題目描述

譯自 CEOI2015 Day2 T1「Ice Hockey World Championship」

今年的世界冰球錦標賽在捷克舉行。\(Bobek\) 已經抵達布拉格,他不是任何團隊的粉絲,也沒有時間觀念。他只是單純的想去看幾場比賽。如果他有足夠的錢,他會去看所有的比賽。不幸的是,他的財產十分有限,他決定把所有財產都用來買門票。

給出 \(Bobek\) 的預算和每場比賽的票價,試求:如果總票價不超過預算,他有多少種觀賽方案。如果存在以其中一種方案觀看某場比賽而另一種方案不觀看,則認為這兩種方案不同。

輸入輸出格式

輸入格式:

第一行,兩個正整數 \(N\)

\(M(1 \leq N \leq 40,1 \leq M \leq 10^{18})\),表示比賽的個數和 \(Bobek\) 那家徒四壁的財產。

第二行,\(N\) 個以空格分隔的正整數,均不超過 \(10^{16}\),代表每場比賽門票的價格。

輸出格式:

輸出一行,表示方案的個數。由於 \(N\) 十分大,注意:答案 \(\le 2^{40}\)

輸入輸出樣例

輸入樣例#1:

5 1000
100 1500 500 500 1000

輸出樣例#1:

8

說明

樣例解釋

八種方案分別是:

  • 一場都不看,溜了溜了
  • 價格 \(100\) 的比賽
  • 第一場價格 \(500\) 的比賽
  • 第二場價格 \(500\) 的比賽
  • 價格 \(100\)
    的比賽和第一場價格 \(500\) 的比賽
  • 價格 \(100\) 的比賽和第二場價格 \(500\) 的比賽
  • 兩場價格 \(500\) 的比賽
  • 價格 \(1000\) 的比賽

有十組資料,每通過一組資料你可以獲得 \(10\) 分。各組資料的資料範圍如下表所示:

資料組號 1-2 3-4 5-7 8-10
$N \leq $ \(10\) \(20\) \(40\) \(40\)
\(M \leq\) \(10^6\) \(10^{18}\) \(10^6\) \(10^{18}\)

題解

首先看資料範圍

  1. 1-4組資料\(N\leq20\),爆搜就可以解決。

    inline void dfs(R ll dep,R ll sum){
     if(sum>m)return;
     if(dep==n+1){
         ans++;
         return;
     }
     dfs(dep+1,sum+a[dep]);
     dfs(dep+1,sum);
    }
    int main(){
     read(n);read(m);
     for(R int i=1;i<=n;i++)read(a[i]);
     if(n<=20){
         dfs(1,0);
         printf("%lld\n",ans);
     }
        return 0;
    }
  2. 5-7組資料\(M\leq10^6\),裸的揹包啊。

    int main(){
     read(n);read(m);
     for(R int i=1;i<=n;i++)read(a[i]);
        if(m<=1e6){
            f[0]=1;
            for(R int i=1;i<=n;i++)
                for(R int j=m;j>=a[i];j--)
                    f[j]+=f[j-a[i]];
            for(R int i=0;i<=m;i++)ans+=f[i];
            printf("%lld\n",ans);
        }
        return 0;
    }
    
  3. 現在你已經能拿到70分了(但在洛谷上是47分)

下面引出主角——折半搜尋(meet in the middle思想)

因為\(N\leq40\) \(O(2^{40})\)的爆搜一定會\(TLE\),所以我們將\(N\)分成兩份

搜尋\(1\)\(n/2\)\(n/2+1\)\(n\),讓複雜度降到\(O(2^{n/2+1})\)

畫一個圖(網上找的不錯的圖)理解一下為什麼能降低複雜度

折半搜尋

折半搜尋2

inline void dfs(R int l,R int r,R ll sum,R ll a[],R ll &cnt){
    if(sum>m)return;
    if(l>r){
        a[++cnt]=sum;
        return;
    }
    dfs(l+1,r,sum+w[l],a,cnt);//選
    dfs(l+1,r,sum,a,cnt);//不選
}

將前一半的搜尋狀態存入a陣列,後一半存入b陣列。

mid=n/2;
dfs(1,mid,0,suma,cnta);
dfs(mid+1,n,0,sumb,cntb);

一般\(meet\) \(in\) \(the\) \(middle\)的難點主要在於最後答案的組合統計。

我們可以現將a或b陣列sort,讓其有序。

然後通過列舉另一個數組中的狀態,來實現統計答案。bobek

上述找\(pos\)的過程可以通過upper_bound()完成。

sort(suma+1,suma+1+cnta);//使一個數組有序
for(R int i=1;i<=cntb;i++)
    ans+=upper_bound(suma+1,suma+1+cnta,m-sumb[i])-suma-1;//統計ans

下面是高清完整code:

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cctype>
#define ll long long
#define R register
#define N 55
using namespace std;
template<typename T>inline void read(T &a){
    char c=getchar();T x=0,f=1;
    while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
    while(isdigit(c)){x=(x<<1)+(x<<3)+c-'0';c=getchar();}
    a=f*x;
}
ll n,m,w[N],mid,suma[1<<21],sumb[1<<21],cnta,cntb,ans;
inline void dfs(R int l,R int r,R ll sum,R ll a[],R ll &cnt){
    if(sum>m)return;
    if(l>r){
        a[++cnt]=sum;
        return;
    }
    dfs(l+1,r,sum+w[l],a,cnt);
    dfs(l+1,r,sum,a,cnt);
}
int main(){
    read(n);read(m);
    for(R int i=1;i<=n;i++)read(w[i]);
    mid=n>>1;
    dfs(1,mid,0,suma,cnta);
    dfs(mid+1,n,0,sumb,cntb);
    sort(suma+1,suma+1+cnta);
    for(R int i=1;i<=cntb;i++)
        ans+=upper_bound(suma+1,suma+1+cnta,m-sumb[i])-suma-1;
    printf("%lld\n",ans);
    return 0;
}

這裡還有一道折半搜尋的好題,難度升級——luogu,還有 my blog.