『笔记』折半搜索 MITM
Meet in the middle算法
我们知道搜索的时间复杂度是 别问我怎么算的,我不会。
所有图片均来自于网络
先来看两张图来模拟一下搜索的场景
假设我们从上面的红点开始进行搜索,找一条能通向下面那个红点的路径,每个点都有两条岔路可供选择那么最坏的情况是把整张图都给走一遍代价十分巨大。
于是乎
红点的部分为起点开始向外搜索到的点,而蓝点表示从终点开始搜索到的点,假设我吗现在搜索到B点,可以发现A点是从起点过来的点,那么久肯定存在一条路径可以从 起点 -> A -> B -> 终点 。
我们可以发现,整个图中的黑色节点和边我们都可以不用访问,优化还是十分明显的
怎么来判断一道题是否要用折半搜索呢 ?
我们先来看一道例题
洛谷:P4799 [CEOI2015 Day2] 世界冰球锦标赛#
题意概括:某人有
思路: 背包,裸的 0-1背包!!!
于是你兴冲冲的写完了代码,心想,好水的蓝题
/*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;
}
然后发现
一看数据范围
但显然会
我们考虑
这时候一共只有
我们分别搜索前一半和后一半,把前一半的状态放进 a数组,后一半的状态放进 b数组,最后来统计答案。
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 是四的倍数的情况下才是等价的(包括负数),
我就被坑了,找了老半天了
难点一般在于最后的最后答案的统计
我们首先将
然后从另一个数组中的状态来实现答案的统计。
再来一张图
我们可以用upper_bound()来实现寻找
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;
/*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取模后最大
思路:枚举暴力
面向数据:
优化前:
考虑
优化后:
我们假设现在有一个数
我们对
因此,现在有两种情况
对于情况
处于- 这种情况下显然
越大越好所以直接两部分结果中取最大的两个加起来取模即可
那么情况
- 依旧是越大越好
/*
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
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通