淺談折半搜尋(meet in the middle)
前言:
在一些N限制為30,40這種搜尋題中,直接暴力搜尋直接T飛,這時候就需要一些優化,折半搜尋就是優化的一種,先搜尋前半部分,再搜尋後半部分,最後合併答案即可,這個演算法難點就是合併答案。時間複雜度在N大了的時候(函式成指數增長),複雜度就會降下去,再加上合併答案所需的時間,大多數情況下比直接暴力要更快(多了?)。
比如,在每一層搜尋時,假如都有兩種選擇,那麼其時間複雜度為。當較大時,往往會導致超時。此時,如果使用折半搜尋,其時間複雜度將縮小為。(引用知乎朝夕的筆記)
例題.
一.luogu4799
今年的世界冰球錦標賽在捷克舉行。Bobek 已經抵達布拉格,他不是任何團隊的粉絲,也沒有時間觀念。他只是單純的想去看幾場比賽。如果他有足夠的錢,他會去看所有的比賽。不幸的是,他的財產十分有限,他決定把所有財產都用來買門票。
給出 Bobek 的預算和每場比賽的票價,試求:如果總票價不超過預算,他有多少種觀賽方案。如果存在以其中一種方案觀看某場比賽而另一種方案不觀看,則認為這兩種方案不同。
程式碼:
#include<bits/stdc++.h> #define ll long long using namespace std; const int maxn=55; ll n,m,w[maxn],suma[1<<21],sumb[1<<21],cnta,cntb,ans; void dfs(int l,int r,ll sum,ll a[],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() { cin>>n>>m; for(int i=1;i<=n;i++) cin>>w[i]; ll mid=n>>1; dfs(1,mid,0,suma,cnta); dfs(mid+1,n,0,sumb,cntb); sort(suma+1,suma+1+cnta); for(int i=1;i<=cntb;i++) ans+=upper_bound(suma+1,suma+1+cnta,m-sumb[i])-suma-1; cout<<ans<<endl; return 0; }
簡要思路:先看到比賽場次N<=40,考慮搜尋,再想到折半搜尋,求出前半、後部分的所有可能取法(中間有剪枝),再將suma陣列排序,sumb中每一個元素在suma中用upper_bound(二分)找出對應可能的答案個數,最後輸出答案總和,這是道較簡單的折半搜尋。
二.
給n個數,從中任意選出一些數,使這些數能分成和相等的兩組。
求有多少種選數的方案。
簡要思路:首先每個數都有三種情況,不取,取放在左集合,取放在右集合(左集合和右集合的數加起來就是一種選數方案(不知道合不合理))。考慮一個集合(選數方案)被分成左集合和右集合,如果左集合的數全部取反,那麼易知這個集合合法當且僅當這個集合所有的數加起來之和為0,這時我們考慮折半深搜,搜完後,兩個部分先排序再利用雙指標合併答案,具體細節在程式碼裡面能體現,是一道折半搜尋的好題,在思維上有所要求。
注:1.最後答案要減1(什麼都不取)
2.要判重(左集合為正、負,右集合為負,正,這事實上是一種取法,這裡利用狀壓和|運算達到判重,但事實上還可以用stl(本蒟蒻不會啊))
程式碼:
#include<bits/stdc++.h> using namespace std; const int maxn=21; int vis[1<<maxn]; struct node { int val,cur; }suma[1<<maxn],sumb[1<<maxn]; int comp1(const node &a,const node &b){return a.val<b.val;} int w[maxn],cnta,cntb; int n; void dfs(int st,int end,int sum,int state) { if(st>end) { if(end==n/2) suma[++cnta].val=sum,suma[cnta].cur=state; else sumb[++cntb].val=sum,sumb[cntb].cur=state; return ; } dfs(st+1,end,sum,state); dfs(st+1,end,sum-w[st],state+(1<<(st-1))); dfs(st+1,end,sum+w[st],state+(1<<(st-1))); } int main() { cin>>n; for(int i=1;i<=n;i++) cin>>w[i]; dfs(1,n/2,0,0); dfs(n/2+1,n,0,0); sort(suma+1,suma+1+cnta,comp1); sort(sumb+1,sumb+1+cntb,comp1); int l=1,r=cntb,ans=0; while(l<=cnta && r>=1) { while(suma[l].val+sumb[r].val>0 && r>=1) r--; int pre=r; while(suma[l].val+sumb[r].val==0 && r>=1) { if(vis[suma[l].cur|sumb[r].cur]==0) vis[suma[l].cur|sumb[r].cur]=1,ans++; r--; } if(suma[l].val==suma[l+1].val) r=pre; l++; } cout<<ans-1; return 0; }
總結:
折半搜尋真有趣,想到搜尋就想想能不能折半搜尋(這個總結還不如不說)。