Meet in the middle

本文深入探讨了折半搜索(meet in the middle)这一搜索优化技术,通过具体例题如CEOI2015 Day2世界冰球锦标赛、USACO12 OPEN平衡的奶牛群及SP4580 ABCDEF,讲解了如何将问题分解,通过预处理和二分查找降低时间复杂度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

搜索是\(OI\)中一个十分基础也十分重要的部分,近年来搜索题目越来越少,逐渐淡出人们的视野。但一些对搜索的优化,例如\(A\)*,迭代加深依旧会不时出现。本文讨论另一种搜索——折半搜索\((meet\ in\ the\ middle)\)

由一道例题引入:CEOI2015 Day2 世界冰球锦标赛

我们可以用以下代码解决\(n\leq 20\)的数据,时间复杂度\(O(2^n)\)

void dfs(int step, int sum)
{
    if (sum>m) return;
    if (step==n+1) {ans++; return;}
    dfs(step+1, sum+a[step]);
    dfs(step+1, sum);
}

\(dfs\)有何弊端?

当搜索层数增加时,时间复杂度增加过快。

可不可以减少搜索层数,甚至降至一半?

当然可以。不然我这篇文章写什么

看网上两张很好的图就一目了然了。

1503934-20190422223207316-1825399647.png

1503934-20190422223222764-138765940.png

于是我们从\(1\)\(n\)搜索\(\frac{n}{2}\)的深度,然后得到两个长为\(2^{\frac{n}{2}}\)的序列,对于第一个排序,然后用第二个在第一个中二分查找并统计答案即可。

(此代码不开\(O2\)在洛谷会\(T\)一个点,在\(loj\)跑的飞快,可能是满屏\(vector\)的缘故。)

#pragma GCC optimize (2)
#include<cstdio>
#include<vector>
#include<algorithm>
#define int long long
#define rep(i, a, b) for (register int i=(a); i<=(b); ++i)
#define per(i, a, b) for (register int i=(a); i>=(b); --i)
using namespace std;
const int N=45;
vector<int> a, b;
int c[N], m, ans, n, mid;

inline int read()
{
    int x=0,f=1;char ch=getchar();
    for (;ch<'0'||ch>'9';ch=getchar()) if (ch=='-') f=-1;
    for (;ch>='0'&&ch<='9';ch=getchar()) x=(x<<1)+(x<<3)+ch-'0';
    return x*f;
}

void dfs1(int step, int now)
{
    if (now>m) return;
    if (step>mid) {a.push_back(now); return;}
    dfs1(step+1, now+c[step]);
    dfs1(step+1, now);
}

void dfs2(int step, int now)
{
    if (now>m) return;
    if (step>n) {b.push_back(now); return;}
    dfs2(step+1, now+c[step]);
    dfs2(step+1, now);
}

signed main()
{
    n=read(); m=read(); mid=n+1>>1;
    rep(i, 1, n) c[i]=read();
    dfs1(1, 0); dfs2(mid+1, 0);
    sort(b.begin(), b.end());
    for (int i:a) ans+=upper_bound(b.begin(), b.end(), m-i)-b.begin();
    printf("%lld\n", ans);
    return 0;
}

再来看另一道例题:USACO12OPEN 平衡的奶牛群

可以看看官方题解

有一种显然的暴力,子集枚举即可, 时间复杂度\(O(3^n)​\),无法通过。

我们把奶牛分为两组:黑色和白色。若\(S\)可行,那么\(S\)可被分为\(A,B\),使得\(sum_{A,black}-sum_{B,black}=sum_{B,white}-sum_{A,white}\)。于是我们可以计算黑色牛每一个子集可能的差值,白色同理。然后对于相同的差值进行配对,统计答案即可。

时间复杂度\(O(3^{\frac{n}{2}}\cdot 2^{\frac{n}{2}})\),即\(O((\sqrt{6})^n)\),可以通过。

依旧满屏\(vector\)

#include<cstdio>
#include<vector>
#include<algorithm>
#define rep(i, a, b) for (register int i=(a); i<=(b); ++i)
#define per(i, a, b) for (register int i=(a); i>=(b); --i)
using namespace std;

inline int read()
{
    int x=0,f=1;char ch=getchar();
    for (;ch<'0'||ch>'9';ch=getchar()) if (ch=='-') f=-1;
    for (;ch>='0'&&ch<='9';ch=getchar()) x=(x<<1)+(x<<3)+ch-'0';
    return x*f;
}

vector<pair<int, int> > solve(vector<int> S)
{
    vector<pair<int, int> > ans;
    int n=S.size(); 
    rep(i, 0, (1<<n)-1)
        for (int j=i; ; j=(j-1)&i)
        {
            int sum=0;
            rep(k, 0, n-1)
                if (j&(1<<k)) sum-=S[k];
                    else if (i&(1<<k)) sum+=S[k];
            if (sum>=0) ans.push_back(make_pair(sum, i));
            if (!j) break;
        }
    sort(ans.begin(), ans.end());
    ans.resize(unique(ans.begin(), ans.end())-ans.begin());
    return ans;
}

int main()
{
    int n=read();
    vector<int> P, Q;
    rep(i, 0, n-1) 
    {
        int x=read();
        if (i&1) P.push_back(x);
            else Q.push_back(x);
    }
    vector<pair<int, int> > L=solve(P), R=solve(Q);
    int p=0, q=0, l=L.size(), r=R.size();
    vector<bool> vis(1<<n);
    while (p<l && q<r)
    {
        if (L[p].first<R[q].first) p++;
        else if (L[p].first>R[q].first) q++;
        else
        {
            int p2=p, q2=q;
            while (p2<l && L[p2].first==L[p].first) p2++;
            while (q2<r && R[q2].first==R[q].first) q2++;
            rep(i, p, p2-1) rep(j, q, q2-1) 
                vis[L[i].second|(R[j].second<<P.size())]=true,
            p=p2; q=q2;
        }
    }
    int ans=count(vis.begin()+1, vis.end(), true);
    printf("%d\n", ans);
    return 0;
}

SP4580 ABCDEF

\(a*b+c=d*(e+f),d\neq 0\)。先枚举前三个,后三个枚举后二分查找即可。

#include<cstdio>
#include<vector>
#include<algorithm>
#define rep(i, a, b) for (register int i=(a); i<=(b); ++i)
#define per(i, a, b) for (register int i=(a); i>=(b); --i)
using namespace std;
vector<int> b, v, w;
int a[105], n; long long ans;

inline int read()
{
    int x=0,f=1;char ch=getchar();
    for (;ch<'0'||ch>'9';ch=getchar()) if (ch=='-') f=-1;
    for (;ch>='0'&&ch<='9';ch=getchar()) x=(x<<1)+(x<<3)+ch-'0';
    return x*f;
}

void prep()
{
    rep(i, 1, n) rep(j, 1, n) rep(k, 1, n)
        b.push_back(a[i]*a[j]+a[k]);
    sort(b.begin(), b.end());
    for (int i=0, j=0; i<b.size(); i=j+1, j++)
    {
        while (j<b.size()-1 && b[j+1]==b[i]) j++;
        v.push_back(b[i]); w.push_back(j-i+1);
    }
}

int check(int x)
{
    int p=lower_bound(v.begin(), v.end(), x)-v.begin();
    if (v[p]==x) return w[p]; else return 0;
}

void calc()
{
    rep(i, 1, n) rep(j, 1, n) rep(k, 1, n)
        if (a[i]) ans+=check((a[j]+a[k])*a[i]);
}

int main()
{
    n=read(); 
    rep(i, 1, n) a[i]=read();
    prep(); calc();
    printf("%lld\n", ans);
    return 0;
}

转载于:https://www.cnblogs.com/ACMSN/p/10752201.html

<think>好的,我现在需要回答用户关于折半搜索(Binary Search)meet in the middle算法的结合或比较的问题,以及它们的原理和应用。首先,我得先理解这两个概念各自是什么,然后看看它们之间的关系和区别。 用户提供的引用中有提到折半搜索和meet in the middle,但看起来有些地方可能混淆了这两个术语。比如引用[1]提到折半搜索是Binary Search,而引用[2]到[5]则把折半搜索和meet in the middle视为同一技术。这里可能存在术语上的差异,需要仔细区分。 首先,Binary Search(折半搜索/二分搜索)通常指的是在有序数组中查找目标值的方法,每次将搜索范围减半,时间复杂度O(log n)。而Meet in the Middle(中间相遇)是一种分治策略,将问题分成两部分分别求解,再合并结果,常用于处理复杂度指数级的问题,比如某些暴力搜索问题。 用户的问题可能集中在两者是否有关联,或者如何结合使用。需要明确的是,虽然两者都有“分治”的思想,但应用场景不同。Binary Search用于查找,而Meet in the Middle用于优化搜索过程,特别是在处理较大规模的问题时,例如拆分搜索空间以减少时间复杂度。 接下来,根据提供的引用,引用[2]到[5]详细描述了Meet in the Middle的应用,比如将整个搜索过程分成两部分,分别处理后合并结果,以降低时间复杂度。例如,当直接暴力搜索的复杂度是O(2^n)时,拆分成两部分后复杂度变为O(2^(n/2)),再加上合并步骤,如排序后使用二分查找或双指针,总体复杂度会大大降低。 需要指出的是,可能用户提到的“折半搜索”在这里实际上指的是Meet in the Middle,而不是传统的Binary Search。因此,在回答时需要澄清术语,避免混淆。比如,中文里“折半搜索”可能被用来翻译两者,但实际是不同的算法。 接下来,整理技术原理和应用: 1. **技术原理**: - **Binary Search**:适用于有序数据,每次比较中间元素,缩小搜索范围。 - **Meet in the Middle**:将问题分解为两个子问题,分别求解所有可能的子集或状态,再合并结果。通常用于优化指数级算法,比如子集和问题、旅行商问题(TSP)的部分情况等。 2. **结合或比较**: - **区别**:Binary Search是查找算法Meet in the Middle是分治策略,用于优化搜索。 - **结合点**:在Meet in the Middle的合并阶段,可能会使用Binary Search来高效查找符合条件的解,例如在合并两个子集的结果时,对其中一个排序后,用Binary Search快速定位。 3. **应用场景**: - Meet in the Middle常用于解决NP难问题,当问题规模较大但可以分割时,例如某些组合优化问题。 - Binary Search则用于查找、确定边界条件等,如旋转数组中的最小值、数值范围的快速定位。 需要引用用户提供的资料中的例子,比如引用[4]提到的世界冰球锦标赛题目,将n场比赛分成两半,分别搜索可能的花费组合,然后合并时用Binary Search找到符合条件的组合数。这说明在Meet in the Middle的合并阶段,Binary Search被用作工具。 另外,引用[5]提到将46次方的问题拆分成两个23次方,复杂度降低,这也是Meet in the Middle的典型应用,而合并时可能需要排序和Binary Search,这展示了二者的结合。 总结回答结构: 1. 澄清术语:区分Binary Search和Meet in the Middle。 2. 原理分述:分别解释两者的工作原理。 3. 比较结合:指出差异及可能的结合点(如合并阶段使用Binary Search)。 4. 应用实例:举例如子集和问题、竞赛题目中的具体应用。 5. 相关问题:生成相关问题供用户进一步学习。 需要确保回答中正确使用LaTeX格式,如时间复杂度写成$O(2^{n/2})$,并引用提供的参考资料中的例子,如引用[4][5]中的案例。 最后,生成的相关问题应围绕原理、应用、优化等方面,帮助用户深入理解。</think>### 折半搜索Meet in the Middle的技术原理及应用解析 #### 一、术语澄清基本原理 1. **折半搜索(Binary Search)** 折半搜索是一种基于**有序数组**的查找算法,通过不断缩小搜索范围至原区间的一半来定位目标值。其时间复杂度为$O(\log n)$,例如在有序数组中查找元素$x$时,每次比较中间值以决定向左或向右继续搜索[^1]。 2. **Meet in the Middle(中间相遇算法)** 这是一种分治策略,将原问题拆分为两个独立的子问题,分别求解所有可能的子状态,最后合并结果。其核心目标是**降低指数级复杂度**,例如将$O(2^n)$优化为$O(2^{n/2})$,并通过合并策略(如二分或双指针)统计最终答案[^2][^5]。 #### 二、技术原理对比结合 | **维度** | **折半搜索(Binary Search)** | **Meet in the Middle** | |------------------|------------------------------|---------------------------------------| | **目标** | 快速定位目标值 | 优化暴力搜索,处理大规模组合问题 | | **输入要求** | 数据必须有序 | 无特殊要求,适用于可分治的问题 | | **时间复杂度** | $O(\log n)$ | $O(2^{n/2} \cdot n)$(合并步骤主导) | | **结合点** | 在Meet in the Middle的合并阶段,利用Binary Search快速筛选有效解[^4] | — | #### 三、Meet in the Middle的核心步骤 1. **拆分问题** 将原问题划分为两个规模相近的子问题,例如将$n$个元素的集合分为前$n/2$和后$n/2$个。 2. **独立搜索** 对每个子集进行暴力搜索,生成所有可能的状态(如子集和),分别存入数组$a$和$b$。 3. **合并结果** - 对数组$b$排序,使其有序化。 - 遍历数组$a$中的每个元素$a_i$,在$b$中使用**Binary Search**找到满足条件(如$a_i + b_j \leq m$)的元素数量[^4]。 **示例**:在“世界冰球锦标赛”问题中,需统计总花费不超过$m$的所有观赛组合。将$n$场比赛分为两半,分别生成所有可能的子集和,合并时通过Binary Search统计有效组合数[^3][^4]。 #### 四、应用场景 1. **子集和问题** 给定集合$S$和目标值$m$,寻找所有子集的和等于$m$。直接暴力搜索复杂度为$O(2^n)$,使用Meet in the Middle可优化至$O(n \cdot 2^{n/2})$[^5]。 2. **密码学中的碰撞攻击** 通过拆分密钥空间,分别计算前一半和后一半的可能值,再寻找匹配结果。 3. **旅行商问题(TSP)** 对于节点数$n$较大的情况,将路径分为两段,分别计算最短路径后合并。 #### 五、优化局限性 - **优势**:将指数级复杂度降为平方根级别,例如$O(2^{46}) \rightarrow O(2^{23})$。 - **局限性**:合并阶段需额外时间(如排序、二分),且空间占用较高(需存储所有子状态)。 ```python # Meet in the Middle示例:统计子集和小于等于m的组合数 def meet_in_middle(arr, m): def dfs(sub_arr, results, idx, current_sum): if idx == len(sub_arr): results.append(current_sum) return dfs(sub_arr, results, idx + 1, current_sum) # 不选当前元素 dfs(sub_arr, results, idx + 1, current_sum + sub_arr[idx]) # 选当前元素 mid = len(arr) // 2 left, right = arr[:mid], arr[mid:] sum_a, sum_b = [], [] dfs(left, sum_a, 0, 0) dfs(right, sum_b, 0, 0) sum_b.sort() count = 0 for s in sum_a: target = m - s # 使用Binary Search找到sum_b中<=target的元素数量 count += bisect_right(sum_b, target) return count ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值