Meet in the Middle学习笔记

本文介绍了MeetintheMiddle算法,一种改进的搜索算法,通过双向搜索减少搜索复杂度。适用于广度优先搜索,尤其当搜索空间呈指数增长时效果显著。文章详细解释了其工作原理,并通过实例展示了如何利用该算法解决实际问题。
部署运行你感兴趣的模型镜像

Meet in the Middle算法

Meet in the Middle算法可以看成是搜索算法的一个改进,一般来说用于广搜(BFS),不过如果搜索深度有上限的情况下也可以用深搜。

我们首先假象一个搜索场景
这里写图片描述

假设从上面的红点开始进行搜索,找一条能通向下面那个红点的路径,每个点都有两条岔路可供选择。

显然如果我们简单的从上面那个点开始BFS,代价是较大的,在最差的情况下,可能需要把整张图的45个节点给走一遍。

当然把搜索的重任全部交给一个节点是不合理的。于是Meet in the Middle算法就诞生了。顾名思义,就是两个节点各自向中间搜索,直至碰头。即双向搜索
这里写图片描述

在上图中红点的部分为起点开始向外搜索到的点,而蓝点表示从终点开始搜索到的点。假如此时搜索进行到点B,直接就能发现A点是从起点过来的点,那么也就自然找到了一条从 起点->A->B->终点 的路径,然而我们发现,整个图里面黑色部分的节点与边其实我们都可以不用访问。当整张图扩大是,这种优化的效果还是比较明显的。

我们接下来用数学来具体推导一下这种搜索方案的复杂度。

假设向外搜索n层需要的代价为f(n)。如果不用meet in the middle 那么复杂度当然是O(f(n))。

如果使用了meet in the middle,那么从起点开始需要搜索的代价为f(n/2),从终点开始搜索的代价也是f(n/2),总代价就是2*f(n/2),复杂度为O(f(n/2))。

当然从这个搜索模型来看,无论是不是使用meet in the middle复杂度均为O(n^2)。但是当f(n)为指数函数时该算法的效果就会非常明显。比如f(n)=2^n,那么不用meet in the middle复杂度即为O(2^n),但是使用了meet in the middle的复杂的只有O(2^(n/2)),相当于直接开了一个根号。meet in the middle算法也有一定的局限性,首先是要求搜索图中的节点状态都是可逆的,即如果A点能到B点,B点也能一下子回到A点,否则就不存在从终点出发这个说法了。

其次是需要额外的数据结构存储找过的节点,一般来说空间足够都采用哈希表。

一般来说,meet in middle基于指数枚举看看数据范围,一般在30左右,即30/2=15,就可以2^15枚举了

题目

NOI2001方程的解数

题解

练一下meet in the middle
暴力搜索:150 ^ 6 =GG
meet in the middle :150 ^3 =能过

计算满足 a+b+c+d+e+f=0 的数的个数

可以算 (a+b+c)+ (d+e+f) = 0

dfs两次,每次dfs一半

结果用双指针逼近法、乘法原理 O(n) 处理

代码

#include <cstdio>
#include <algorithm>
using namespace std;
const int N=10;
const int M=3375005;
int mb,n,m,k[N],p[N],tmp1[M],tmp2[M],sum1,sum2;
int ksm(int a,int k)
{
    int ans=1;
    for (;k;k>>=1,a=a*a)
        if (k&1) ans=ans*a;
    return ans;
}
void dfs(int t,int tot,int *tmp,int &sum)
{
    if (t>mb) {tmp[++sum]=tot;return;}
    for (int i=1;i<=m;i++) dfs(t+1,tot+k[t]*ksm(i,p[t]),tmp,sum);
}
int work()
{
    int ans=0;
    sort(tmp1+1,tmp1+sum1+1);
    sort(tmp2+1,tmp2+sum2+1);
    int l=1,r=sum2;
    for (;l<=sum1 && r>1;l++)
    {
        while (tmp1[l]+tmp2[r]>0) r--;
        int ll=1,rr=0;
        for (int j=r;tmp1[l]+tmp2[j]==0 && j>0;j--) rr++;
        while (l<sum1 && tmp1[l]==tmp1[l+1]) ll++,l++;
        ans+=ll*rr;
    }
    return ans;
}
int main()
{
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;i++) scanf("%d%d",&k[i],&p[i]);
    mb=n/2;
    dfs(1,0,tmp1,sum1);
    mb=n;
    dfs(n/2+1,0,tmp2,sum2);
    printf("%d",work());
}

部分引自这个up

您可能感兴趣的与本文相关的镜像

LobeChat

LobeChat

AI应用

LobeChat 是一个开源、高性能的聊天机器人框架。支持语音合成、多模态和可扩展插件系统。支持一键式免费部署私人ChatGPT/LLM 网络应用程序。

<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 ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值