折半搜尋【p4799】[CEOI2015 Day2]世界冰球錦標賽
阿新 • • 發佈:2018-11-24
Description
今年的世界冰球錦標賽在捷克舉行。Bobek 已經抵達布拉格,他不是任何團隊的粉絲,也沒有時間觀念。他只是單純的想去看幾場比賽。如果他有足夠的錢,他會去看所有的比賽。不幸的是,他的財產十分有限,他決定把所有財產都用來買門票。
給出 Bobek 的預算和每場比賽的票價,試求:如果總票價不超過預算,他有多少種觀賽方案。如果存在以其中一種方案觀看某場比賽而另一種方案不觀看,則認為這兩種方案不同。
Input
第一行,兩個正整數 \(N\) 和 \(M\)\((1 \leq N \leq 40,1 \leq M \leq 10^{18})\),表示比賽的個數和 Bobek 那家徒四壁的財產。
第二行,\(N\) 個以空格分隔的正整數,均不超過 \(10^{16}\),代表每場比賽門票的價格。
Output
輸出一行,表示方案的個數。由於 \(N\) 十分大,注意:答案 \(\le 2^{40}\)
顯然這個題直接dfs是過不去的\(O(2^n)\)
但是我們可以一半一半的搜,即折半搜尋,複雜度可以降到\(O(2^{\frac{n}{2}})\)
所以我們取一個\(mid\),分別搜前半段和後半段。
然後合併答案的時候就需要令某一個數組變得有序,在其中找到最靠右的合法位置,直接累加即可。
這裡用到了\(upper\)_\(bound\)
程式碼
#include<cstdio> #include<iostream> #include<algorithm> #define R register #define lo long long using namespace std; const int gz=1e6+6e5; inline void in(R lo &x) { R int f=1;x=0;char s=getchar(); while(!isdigit(s)){if(s=='-')f=-1;s=getchar();} while(isdigit(s)){x=x*10+s-'0';s=getchar();} x*=f; } lo a[gz],b[gz],mon[42],ans,m; int sum,cnt,n,mid; void dfs(R int dep,R lo now) { if(now>m)return; if(dep>mid) { a[++cnt]=now; return; } dfs(dep+1,now+mon[dep]); dfs(dep+1,now); } void dfss(R int dep,R lo now) { if(now>m)return; if(dep>n) { b[++sum]=now; return; } dfss(dep+1,now+mon[dep]); dfss(dep+1,now); } int main() { scanf("%d%lld",&n,&m); for(R int i=1;i<=n;i++)in(mon[i]); mid=(n+1)/2; dfs(1,0);dfss(mid+1,0); sort(b+1,b+sum+1); for(R int i=1;i<=cnt;i++) ans+=upper_bound(b+1,b+sum+1,m-a[i])-b-1; printf("%lld",ans); }