ヒマラヤ.神眷の樱花

『笔记』折半搜索 MITM

EVER_LASTING·2022-07-30 10:09·122 次阅读

『笔记』折半搜索 MITM

Meet in the middle算法

meet in the middle 即折半搜索,顾名思义,将原有数据分成两部分,最后在中间合并的算法。(是 meet in the middil 不是 binary search 或者 half-interval search 那是二分 )
我们知道搜索的时间复杂度是 2nn 过大时显然他会超时,而折半搜索可以把时间降到 2n/21 别问我怎么算的,我不会

所有图片均来自于网络

先来看两张图来模拟一下搜索的场景
image

假设我们从上面的红点开始进行搜索,找一条能通向下面那个红点的路径,每个点都有两条岔路可供选择那么最坏的情况是把整张图都给走一遍代价十分巨大。
于是乎 Meet in the Middle 算法诞生了

image

红点的部分为起点开始向外搜索到的点,而蓝点表示从终点开始搜索到的点,假设我吗现在搜索到B点,可以发现A点是从起点过来的点,那么久肯定存在一条路径可以从 起点 -> A -> B -> 终点
我们可以发现,整个图中的黑色节点和边我们都可以不用访问,优化还是十分明显的

怎么来判断一道题是否要用折半搜索呢 ? n 较小(一般 n40),而答案的范围非常的大

我们先来看一道例题

洛谷:P4799 [CEOI2015 Day2] 世界冰球锦标赛#

题意概括:某人有 m 元,共有 n 场比赛,门票价格为 c[i] 求购票方案(一场也不买也算一种)
思路: 背包,裸的 0-1背包!!!
于是你兴冲冲的写完了代码,心想,好水的蓝题

Copy
/*P4799 背包 [CEOI2015 Day2] 世界冰球锦标赛 author: Rmax | Always_maxx */ #include<bits/stdc++.h> #include<cmath> #include<queue> #include<cstdio> #include<cstring> #include<iostream> #define gc getchar #include<algorithm> #define reg register #define ll long long #define int long long //#pragma GCC optimize(2) using namespace std; const int N=1e6+5; const int mod = 1e9+7; const int INF = 0x3f3f3f3f; inline void print(int x) {if (x < 0) putchar('-'), x = -x; if(x > 9) print(x / 10); putchar(x % 10 + '0');} inline int read() { int res = 0, f = 0; char ch = gc();for (; !isdigit(ch); ch = gc()) f |= (ch == '-'); for (; isdigit(ch); ch = gc()) res = (res << 1) + (res << 3) + (ch ^ '0'); return f ? -res : res;} int n,m,ans,f[N],a[N]; signed main() { n=read(),m=read(); for(reg int i=1;i<=n;i++) a[i]=read(); f[0]=1; for(reg int i=1;i<=n;i++) for(reg int j=m;j>=a[i];j--) f[j]+=f[j-a[i]];//普普通通的 0-1 背包 for(reg int i=0;i<=m;i++) ans+=f[i]; printf("%lld",ans); return 0; }

然后发现
image

一看数据范围
(1N40,1M1018) 答案 240 (240=10995116277761012)
N40 嗯,暴力的机会来了
但显然会 T
我们考虑 Meet in the Middle 优化
这时候一共只有 2×2202×106

我们分别搜索前一半和后一半,把前一半的状态放进 a数组,后一半的状态放进 b数组,最后来统计答案。

Copy
dfs(1,n/2,0,s1,c1); dfs(n/2+1,n,0,s2,c2);

不要用位运算,不要用位运算,不要用位运算!!!
n/2 和 n>>1 在有负数时是有区别的
/ 运算在正整数时是向下取整,负数的时候只是在正数结果前加一个负号,而位运算一直都是向下取整。
另外 n/2+1 和 n>>1|1 只有在 n 是四的倍数的情况下才是等价的(包括负数),
我就被坑了,找了老半天了......
难点一般在于最后的最后答案的统计
我们首先将 a 数组或 b 数组进行 sort ,使其有序。
然后从另一个数组中的状态来实现答案的统计。
再来一张图
image

我们可以用upper_bound()来实现寻找 pos 的过程

Copy
sort(s1+1,s1+c1+1); for(reg int i=1;i<=c2;i++) ans+=upper_bound(s1+1,s1+c1+1,m-s2[i])-s1-1;

Code

Copy
/*P4799 折半搜索 [CEOI2015 Day2] 世界冰球锦标赛 author: Rmax */ #include<bits/stdc++.h> #include<cmath> #include<queue> #include<cstdio> #include<cstring> #include<iostream> #define gc getchar #include<algorithm> #define reg register #define int long long using namespace std; const int N=1e7+5; const int mod = 1e9+7; const int INF = 0x3f3f3f3f; inline int read() { int res = 0, f = 0; char ch = gc();for (; !isdigit(ch); ch = gc()) f |= (ch == '-'); for (; isdigit(ch); ch = gc()) res = (res << 1) + (res << 3) + (ch ^ '0'); return f ? -res : res;} int n,m,c1,c2,ans,tic[45],s1[N],s2[N]; inline void dfs(int str,int end,int sum,int a[],int &num) { if(sum>m) return; if(str>end) { a[++num]=sum;//压进a数组里面 return; } dfs(str+1,end,sum+tic[str],a,num);//选择该门票 dfs(str+1,end,sum,a,num);//不选该门票 } signed main() { n=read(),m=read(); for(reg int i=1;i<=n;i++) tic[i]=read(); dfs(1,n/2,0,s1,c1);//前一半的搜索状态存进s1数组 dfs(n/2+1,n,0,s2,c2);//后一半的搜索状态存进s2数组 sort(s1+1,s1+c1+1);//排序s1数组使其有序 for(reg int i=1;i<=c2;i++) ans+=upper_bound(s1+1,s1+c1+1,m-s2[i])-s1-1;//枚举第一个x+a[pos]>m的位置,pos-1前面的状态都能产生贡献 printf("%lld",ans); return 0; }

CF888E Maximum Subsequence#

题意:给一个数列n和模数m,在数列任选若干个数,使得他们的和对m取模后最大
思路:枚举暴力 2n ?
面向数据:n35,m109,a[i]109
优化前:235=34359738368 肯定会 T

考虑 Meet in the Middle 优化

优化后: 218=262114

我们假设现在有一个数 a 是前半部分 dfs 中的,一个数 b 是后半部分 dfs 中的.
我们对 m 取模后,显而易见, a<m , b<m 所以 a+b<2×m
因此,现在有两种情况
对于情况 1

  • a+b 处于 [m,2m)
  • 这种情况下显然 a+b 越大越好所以直接两部分结果中取最大的两个加起来取模即可

那么情况 2 就为剩下的部分

  • [0,m)
  • 依旧是越大越好

Code

Copy
/* CF888E Maximum Subsequence author: Rmax | Always_maxx */ #include<bits/stdc++.h> #include<cmath> #include<queue> #include<cstdio> #include<cstring> #include<iostream> #define gc getchar #include<algorithm> #define reg register #define ll long long //#define int long long using namespace std; const int N=5e6+5; const int mod = 1e9+7; const int INF = 0x3f3f3f3f; inline void print(int x) {if (x < 0) putchar('-'), x = -x; if(x > 9) print(x / 10); putchar(x % 10 + '0');} inline int read() { int res = 0, f = 0; char ch = gc();for (; !isdigit(ch); ch = gc()) f |= (ch == '-'); for (; isdigit(ch); ch = gc()) res = (res << 1) + (res << 3) + (ch ^ '0'); return f ? -res : res;} int w[45],a[N],b[N]; int n,m,cnt1,cnt2,mid,ans; inline void dfs1(int now,int sum) { if(now>mid) { a[++cnt1]=sum; return ; } dfs1(now+1,sum); dfs1(now+1,(sum+w[now])%m); } inline void dfs2(int now,int sum) { if(now>n) { b[++cnt2]=sum; return; } dfs2(now+1,sum); dfs2(now+1,(sum+w[now])%m); } signed main() { n=read(),m=read(); for(reg int i=1;i<=n;i++) w[i]=read(); if(n==1) { printf("%d",w[1]%m); return 0; } mid=n/2; dfs1(1,0); dfs2(mid+1,0); sort(a+1,a+cnt1+1); sort(b+1,b+cnt2+1); int l=0,r=cnt2; while(l<=cnt1) { while(a[l]+b[r]>=m) r--; ans=max(ans,a[l]+b[r]); l++; } ans=max(ans,(a[cnt1]+b[cnt2])%m); printf("%d",ans); return 0; }

推荐例题

  • Lights G 洛谷 P2962
  • Jurassic Remains POJ 1903
  • 平衡的奶牛群 洛谷 P3067
  • ABCDEF SPOJ 4580
posted @   Always_maxx  阅读(122)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
点击右上角即可分享
微信分享提示
目录