[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:
輸出樣例#1:
說明
樣例解釋
八種方案分別是:
- 一場都不看,溜了溜了
- 價格 \(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-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; }
-
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; }
-
現在你已經能拿到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})\) 。
畫一個圖(網上找的不錯的圖)理解一下為什麼能降低複雜度
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,讓其有序。
然後通過列舉另一個數組中的狀態,來實現統計答案。
上述找 \(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; }
這裡還有一道折半搜尋的好題,難度升級—— ofollow,noindex" target="_blank">luogu ,還有my blog.