折半搜索

简介:

\(\mathbf{meet}\) \(\mathbf{in}\) \(\mathbf{the}\) \(\mathbf{middle}\) 是一种特殊的搜索技巧,利用了“折半”这种思想,先分别搜出两半子问题的答案,再利用总问题与子问题之间的关系进行合并,得出答案。

关于时间复杂度:

原问题的搜索往往是 \(\Theta(2^n)\) 复杂度,折半后复杂度同时折半,当然还要算上合并答案的复杂度(这类复杂度往往不会太高)。

练习:

世界冰球锦标赛

当然是 \(\mathbf{meet}\) \(\mathbf{in}\) \(\mathbf{the}\) \(\mathbf{middle}\) 的模板题,首先爆搜不用多说,考虑如何合并答案:我们将前后两半子问题的每种状态搜出来,再将前半段状态按照答案排序,利用 upper_bound 查出前驱进行统计。

code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=55,M=1<<21;
int n,m,w[N],mid,suma[M],sumb[M],cnta,cntb,ans;
void dfs(int l,int r,int sum,int a[],int &cnt){
    if(sum>m) return;
    if(l>r) return a[++cnt]=sum,void();
    dfs(l+1,r,sum+w[l],a,cnt);
    dfs(l+1,r,sum,a,cnt);
}
signed main(){
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>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(int i=1;i<=cntb;i++)
        ans+=upper_bound(suma+1,suma+1+cnta,m-sumb[i])-suma-1;
    cout<<ans;
    return 0;
}

期末考试

知道了 \(\mathbf{meet}\) \(\mathbf{in}\) \(\mathbf{the}\) \(\mathbf{middle}\) 这题就比较容易了,首先爆搜是平凡的,枚举每个状态一个一个对号就行,但是超时。因此考虑如何合并答案:我们为前半子问题的每一个答案开桶计数,对后半子问题的每个答案查 \(m-sum\) 即可,当然这 \(n\) 次的回答是统一的,不能分开统计,因此我们计算对于 \(n\) 次回答每个最终的得分并将这 \(n\) 个数哈希起来,统一考虑即可。

code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e4+10,p=1e9+7;
int n,ans(0),hast1(0);
int d[N],b[N][15],a[N];
map<int,int> mp;
void dfs(int x){
    if(x==6){
        int has(0);
        for(int i=1;i<=n;i++){
            int op=0;
            for(int j=1;j<=5;j++) if(d[j]==b[i][j]) op+=10;
            if(op>a[i]) return;
            has=(has*131%p+op)%p;
        }
        mp[has]++;
        return;
	}
	d[x]=1;dfs(x+1);
    d[x]=2;dfs(x+1);
	d[x]=3;dfs(x+1);
    d[x]=4;dfs(x+1);
}
void dfs1(int x){
    if(x==11){
	    int has(0);
        for(int i=1;i<=n;i++){
            int op=0;
            for(int j=6;j<=10;j++) if(d[j]==b[i][j]) op+=10;
            if(op>a[i]) return;
            has=(has*131%p+op)%p;
		}
		ans+=mp[(hast1-has+p)%p];
		return;
	}
	d[x]=1;dfs1(x+1);
    d[x]=2;dfs1(x+1);
	d[x]=3;dfs1(x+1);
    d[x]=4;dfs1(x+1);
}
char s[N][15];
signed main(){
    // freopen("1.in","r",stdin);
    ios::sync_with_stdio(false);
    // cin.tie(0),cout.tie(0);
    int T;cin>>T;
    while(T-->0){
        cin>>n;
        hast1=0;
        mp.clear();
        for(int i=1;i<=n;++i){
            for(int j=1;j<=10;++j) cin>>s[i][j];
            for(int j=1;j<=10;++j){
                if(s[i][j]=='A') b[i][j]=1;
                if(s[i][j]=='B') b[i][j]=2;
                if(s[i][j]=='C') b[i][j]=3;
                if(s[i][j]=='D') b[i][j]=4;
            }
            cin>>a[i];
            hast1=(hast1*131%p+a[i])%p;
        }
        ans=0;
        dfs(1);dfs1(6);
        cout<<ans<<endl;
    }
}

方程的解数

裸的 \(\mathbf{meet}\) \(\mathbf{in}\) \(\mathbf{the}\) \(\mathbf{middle}\),没啥好说的,权当练习吧。

code
#include<bits/stdc++.h>
using namespace std;
int n,m,mid,ans(0);
int k[10],p[10],x[10];
map<int,int> mp;
void dfs1(int dep,int sum){
    if(dep==mid+1){
        mp[sum]++; 
        return;
    }
    for(int i=1;i<=m;++i)
        dfs1(dep+1,sum+pow(i,p[dep])*k[dep]);
}
void dfs2(int dep,int sum){
    if(dep==n+1){
        ans+=mp[-sum]; 
        return;
    }
    for(int i=1;i<=m;++i)
        dfs2(dep+1,sum+pow(i,p[dep])*k[dep]);
}
int main(){
    cin>>n>>m;
    mid=n>>1;
    for(int i=1;i<=n;++i) cin>>k[i]>>p[i];
    dfs1(1,0);
    dfs2(mid+1,0);
    cout<<ans;
}

买瓜

首先爆搜不用说,别读错题就行(可以不买瓜,希望没人和我一样读错)。答案的合并是比较套路的 \(\mathbf{meet}\) \(\mathbf{in}\) \(\mathbf{the}\) \(\mathbf{middle}\),直接开桶维护即可。但是这样还是不足以通过本题,因此我们考虑剪枝:

  1. 最优性剪枝:如果当前答案已经大于最优解,剪掉。
  2. 可行性剪枝:如果当前答案已经超标,剪掉。
  3. 优化搜索顺序:这没啥好说的,\(sort\) 一下就行。
code
#include<bits/stdc++.h>
#define int long long
#define inf 1e9+10
using namespace std;
const int N=50;
int n,m,ans(inf),mid;
int a[N];
unordered_map<int,int> mp;
void dfs1(int dep,int sum,int num){
    if(sum>m||num>ans) return;
    if(sum==m) return ans=min(ans,num),void();
    if(dep==mid+1){
        if(mp.count(sum)) mp[sum]=min(mp[sum],num);
        else mp[sum]=num;
        return;
    }
    dfs1(dep+1,sum,num);
    dfs1(dep+1,sum+a[dep],num);
    dfs1(dep+1,sum+a[dep]/2,num+1);
}
void dfs(int dep,int sum,int num){
    if(sum>m||num>ans) return;
    if(sum==m) return ans=min(ans,num),void();
    if(dep==n+1){
        if(mp.count(m-sum)) ans=min(ans,mp[m-sum]+num);
        return;
    }
    dfs(dep+1,sum,num);
    dfs(dep+1,sum+a[dep],num);
    dfs(dep+1,sum+a[dep]/2,num+1);
}
signed main(){
    // freopen("1.in","r",stdin);
    scanf("%lld%lld",&n,&m);
    m<<=1;
    mid=max(1ll,n/2);
    for(int i=1;i<=n;++i) scanf("%lld",a+i);
    sort(a+1,a+n+1);
    for(int i=1;i<=n;++i) a[i]<<=1;
    dfs1(1,0,0);
    dfs(mid+1,0,0);
    if(ans>=inf) return puts("-1")&0;
    cout<<ans;
    return 0;
}
posted @ 2023-09-18 18:05  Melting_Pot  阅读(39)  评论(1编辑  收藏  举报