1. 程式人生 > 其它 >淺談折半搜尋(meet in the middle)

淺談折半搜尋(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;
}

總結:

折半搜尋真有趣,想到搜尋就想想能不能折半搜尋(這個總結還不如不說)。