折半搜索 meet in the middle

折半搜索

引入:

一般来说,很多元素少的题都可以用暴力搜索直接写。

比如说枚举每个点的使用情况...每个物品的使用....这类问题,时间复杂度为 \(O(2^n)\)

但是,当给定的 \(n\) 变大时,我们应该怎么办,这就引出了折半搜索( \(meet in the middle\) ).

定义:

主要思想是把整个搜索过程分为两半,分别搜索,最后将两半的结果合并。

这样可以把时间复杂度降到 \(O(n2^{n/2})\) 水平。

使用:

一般来说,就是将前一半搜索后的条件和情况,分组存到 \(map,vector,set\) 等存储器中

进行后一半搜索时,判断其中有没有条件和情况与之相匹配,进行记录。

可以通过队列,递归解决搜索时的问题,一定要灵活运用

例题:

[USACO09NOV]Lights G

这道题看数据范围,典型的卡暴力搜索时间,因此使用折半搜索。

通过二进制存储点的状态,开 \(long long\) ,然后记录操作情况避免重复即可。

// P2962 [USACO09NOV]Lights G

#include<bits/stdc++.h>
using namespace std;
#define ll long long 
#define  pll pair<ll,ll>
const int N=605;
vector<int> e[N];
map<ll,int> maps[2];
int val[N],mul[N];
int n,m;

inline ll change(ll ch,int x){
    if((ch>>x)&1) return ch-((ll)1<<x);
    return ch+((ll)1<<x);
}
queue<pll >q;
int solve(){
    ll now=((ll)1<<n)-1;//完美情况,所有都变成1
    q.push({0,0});
    while(!q.empty()){
        pll p=q.front(); q.pop();
        int k=maps[0][p.first];
        for(int i=0;i<n/2;i++){
            if((p.second>>i)&1) continue;//当前已经亮过,避免重复开关
            pll pp=p; 
            pp.second+=((ll)1<<i); pp.first=change(pp.first,i);//将此处开灯后的变化
            for(auto y:e[i]) pp.first=change(pp.first,y);//连接的边改变
            if(maps[0].count(pp.first)==0){//没有这种亮灯情况
                maps[0][pp.first]=k+1;//存储操作次数
                q.push(pp);//将这种亮灯情况继续扩展
            }
        }
    }
    q.push({0,0});
    while(!q.empty()){
        pll p=q.front(); q.pop();
        int k=maps[1][p.first];
        for(int i=n/2;i<n;i++){
            if((p.second>>i)&1) continue;
            pll pp=p; 
            pp.second+=((ll)1<<i); pp.first=change(pp.first,i);
            for(auto y:e[i]) pp.first=change(pp.first,y);
            if(maps[0].count(pp.first^now)) return maps[0][pp.first^now]+k+1;//0中有匹配的情况
            if(maps[1].count(pp.first)==0) maps[1][pp.first]=k+1,q.push(pp);//不行,也存储
        }
    }
    return -1;
}

int main(){
    cin>>n>>m;  
    mul[0]=1; for(int i=1;i<n;i++) mul[i]=mul[i-1]*2;
    for(int i=1,x,y;i<=m;i++){
        scanf("%d%d",&x,&y);  x--,y--;
        e[x].push_back(y);e[y].push_back(x);
    }
    printf("%d\n",solve());
    system("pause");
    return 0;
}

P4799 [CEOI2015 Day2]世界冰球锦标赛

依旧是熟悉的数据范围。

我们将前一半的情况存到 \(vector[1]\) 中,进行从小到大排序.

将后一半 \(vector[2]\) 中的元素与之匹配,两者之和小于 \(m\) 即可,这里可以用 \(upperbound\) 轻松解决。

// P4799 [CEOI2015 Day2]世界冰球锦标赛
#include<bits/stdc++.h>
using namespace std;
#define int long long  
const int N=50;
int n,m;
int val[N],ans,len;
vector<int> a,b;
void dfs(int st,int en,int sum,vector<int> &now){
    if(sum>m) return;
    if(st>en){
        now.push_back(sum); return;
    }
    dfs(st+1,en,sum+val[st],now); dfs(st+1,en,sum,now);
}


signed main(){
    cin>>n>>m;
    for(int i=1;i<=n;i++) scanf("%lld",&val[i]);
    dfs(1,n/2,0,a); dfs(n/2+1,n,0,b);
    sort(a.begin(),a.end());
    len=b.size();
    for(int i=0;i<len;i++) 
        ans+=upper_bound(a.begin(),a.end(),m-b[i])-a.begin();
    //搜索在 A 中的元素有多少可以和 B 的情况匹配,这里是返回小于等于的下标,因此可以看成元素的个数
    cout<<ans<<endl;
    system("pause");
    return 0;
}

[USACO12OPEN]Balanced Cow Subsets G

虽然看着不是折半搜索,但是确实是折半搜索。

我们搜索前一半,记录加和为正数/负数情况(因为后一半可以进行补充)

这样就避免了只从前一半/后一半选取数字选不到的情况.

//P3067 [USACO12OPEN]Balanced Cow Subsets G

#include<bits/stdc++.h>
using namespace std;
#define int long long 
const int N=2e6+7;
int n,val[25],vis[N],ans,mid,cnt;

vector<int> d[N];
map<int,int> M;

void dfs1(int i,int sum,int now){
    if(i>mid){
        if(!M.count(sum)) M[sum]=++cnt;//map离散化编号
        d[M[sum]].push_back(now);//当前状态可以拼出来sum的和
        return;
    }
    dfs1(i+1,sum,now);
    dfs1(i+1,sum+val[i],now|(1<<(i-1)));
    dfs1(i+1,sum-val[i],now|(1<<(i-1)));//这里可以看成插了负数
}

void dfs2(int i,int sum,int now){
    if(i>n){
        if(M.count(sum)){
            int x=M[sum];
            for(auto y:d[x]) vis[y|now]=1;
        }
        return;
    }
    dfs2(i+1,sum,now);
    dfs2(i+1,sum+val[i],now|(1<<(i-1)));
    dfs2(i+1,sum-val[i],now|(1<<(i-1)));
}

signed main(){
    cin>>n; mid=n/2;
    for(int i=1;i<=n;i++) cin>>val[i];
    dfs1(1,0,0); 
    dfs2(mid+1,0,0);
    for(int i=1;i<=(1<<n);i++) ans+=vis[i];
    cout<<ans<<endl;
    system("pause");
    return 0;
}
posted @ 2021-09-26 08:41  Evitagen  阅读(111)  评论(0编辑  收藏  举报