Meet in the middle
Meet in the middle
俗称折半搜索,将 m^n复杂度可降成 m^(n/2)
1.luoguP5691 [NOI2001] 方程的解数
朴素算法的复杂度为m^n m=150,n=6 稳稳地超时
but采用Meet in the middle 就可以将复杂度降成m^(n/2) 此时可过
考虑将式子拆成 1 ~ n/2 与 n/2+1 ~ n 项的两个部分A与B
方程为A+B=0,此时将其中一部分移项过去,A=-B
两边分别算,算完之后看B中的合法解是否有对应的相反数-B,出现在A中
累计和即可
方程的解可能很大,要用map或者手写Hash统计其对应的出现次数
//luoguP5691 [NOI2001] 方程的解数
#include<bits/stdc++.h>
#include<tr1/unordered_map>
using namespace std;
typedef long long ll;
const ll N=151;
inline ll read() {
ll x=0,f=1;
char ch=getchar();
while(ch<48||ch>57) {
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>=48&&ch<=57) {
x=(x<<3)+(x<<1)+(ch^48);
ch=getchar();
}
return x*f;
}
inline ll ksm(ll a,ll b) {
ll ans=1,p=a;
while(b) {
if(b&1) ans*=p;
p*=p;
b>>=1;
}
return ans;
}
ll n,m,cnt=0;
ll k[N],p[N];
tr1::unordered_map<ll,ll> mp;
tr1::unordered_map<ll,ll>::iterator it;
inline void dfs(ll dep,ll x) {
for(register ll i=1;i<=m;i++) {
ll tmp=x+(k[dep]*ksm(i,p[dep]));
if(dep==(n/2)) {
mp[tmp]++;
continue;
} else if(dep==n) {
it=mp.find(-tmp);
if(it!=mp.end()) cnt+=it->second;
continue;
}
dfs(dep+1,tmp);
}
return ;
}
int main() {
n=read();
m=read();
for(register ll i=1; i<=n; i++) {
k[i]=read();
p[i]=read();
}
dfs(1,0);
dfs(n/2+1,0);
printf("%lld\n",cnt);
return 0;
}
2.好题CF888E Maximum Subsequence
首先不妨处理出可以合并出来的取模结果
分成前后两段,就是一个朴素的Meet in the middle
两部分之和显然小于2m,
对于和,可以进行一个非常妙的分讨
1.[0,m)中,前后排完序,对于前半部分l,选择最大的(即后半部分r与其相加最大的那个,和<=m)加起来就好了!!!
2.[m,2m)中,直接取最大值即可,一步比较即可结束!!!
可以用双指针实现1,决策的单调性显然
//CF888E 印象深刻的题号
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
inline ll read() {
ll x=0,f=1;
char ch=getchar();
while(ch<48||ch>57) {
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>=48&&ch<=57) {
x=(x<<3)+(x<<1)+(ch^48);
ch=getchar();
}
return x*f;
}
ll n,m;
ll a[51];
ll ans=0;
vector<ll> p[2];
void dfs(ll op,ll x,ll sum,ll lim) {
sum%=m;
if(x>lim) {
p[op].push_back(sum);
return ;
}
dfs(op,x+1,sum+a[x],lim);
dfs(op,x+1,sum,lim);
}
int main() {
n=read();
m=read();
for(ll i=1; i<=n; i++) {
a[i]=read();
a[i]%=m;
}
dfs(0,1,0,(n>>1));
dfs(1,(n>>1)+1,0,n);
sort(p[0].begin(),p[0].end());
sort(p[1].begin(),p[1].end());
ll l=0,r=p[1].size()-1;
ans=max(ans,(p[0][l]+p[1][r])%m);
while(l<p[0].size()&&r>=0) {
while(p[0][l]+p[1][r]>=m) r--;
ans=max(ans,(p[0][l]+p[1][r])%m);
l++;
}
printf("%lld\n",ans);
return 0;
}
3.luoguP4799 [CEOI2015 Day2] 世界冰球锦标赛
这一题和CF888E真的很像,折半搜索照搬
决策的单调性更加显然
考虑前后半部分可得值排序后,与前半部分的l不满足的后半部分
对于l+1...等也都不满足
同时由于序列有序,可以满足l的只有后半部分1~r
还要考虑自己本身也可以不选后半部分这一方案
双指针修改区间范围后,ans+=r+1即可
//P4799 [CEOI2015 Day2] 世界冰球锦标赛
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N=51;
ll read() {
ll x=0,f=1;
char ch=getchar();
while(ch<48||ch>57) {
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>=48&&ch<=57) {
x=(x<<3)+(x<<1)+(ch^48);
ch=getchar();
}
return x*f;
}
ll n,m,ans=0,a[N];
vector<ll> p[2];
void dfs(ll op,ll x,ll sum,ll lim) {
if(sum>m) return ;
if(x>lim) {
p[op].push_back(sum);
return ;
}
dfs(op,x+1,sum+a[x],lim);
dfs(op,x+1,sum,lim);
}
int main() {
n=read();
m=read();
for(ll i=1;i<=n;i++) {
a[i]=read();
}
dfs(0,1,0,n/2);
dfs(1,n/2+1,0,n);
sort(p[0].begin(),p[0].end());
sort(p[1].begin(),p[1].end());
ll l=0,r=p[1].size()-1;
while(l<p[0].size()&&r>=0) {
while(p[0][l]+p[1][r]>m) r--;
ans+=r+1;
l++;
}
printf("%lld\n",ans);
return 0;
}
4.luoguP3067 [USACO12OPEN]Balanced Cow Subsets G
刚拿到这题的时候,思维被“折半”限制住了,认为只能分两组
看了题解之后,发现每一头奶牛都有放在a组,放在b组,不放任何一组三种情况
然后可以将奶牛分成两半,分别计算三种情况
时间复杂度O(3^(n/2))
假设在前一半中,在第一组中放的数的和为a,在第二组中放的数为b。
假设在后一半中,在第一组中放的数的和为c,在第二组中放的数为d。
那么a+c=b+d
由于我们要对每一半分开处理,所以考虑将同一半的数放在一起处理,即移项得a-b=c-d
这个分组和移项非常的妙,只在其中一半区间的数也可以被包含了进去
//P3067 [USACO12OPEN]Balanced Cow Subsets G
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N=2e6+10;
ll read() {
ll x=0,f=1;
char ch=getchar();
while(ch<48||ch>57) {
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>=48&&ch<=57) {
x=(x<<3)+(x<<1)+(ch^48);
ch=getchar();
}
return x*f;
}
ll n,m,ans=0,a[N],tot=0,v[N];
vector<ll> p[N];
map<ll,ll> b;
void dfs1(ll x,ll sum,ll now,ll lim) {
if(x>lim) {
if(!b[sum]) b[sum]=++tot;
p[b[sum]].push_back(now);
return ;
}
dfs1(x+1,sum+a[x],now|(1<<(x-1)),lim);
dfs1(x+1,sum-a[x],now|(1<<(x-1)),lim);
dfs1(x+1,sum,now,lim);
}
void dfs2(ll x,ll sum,ll now,ll lim) {
if(x>lim) {
ll u=b[sum];
for(ll i=0;i<p[u].size();i++) v[p[u][i]|now]=1;
return ;
}
dfs2(x+1,sum+a[x],now|(1<<(x-1)),lim);
dfs2(x+1,sum-a[x],now|(1<<(x-1)),lim);
dfs2(x+1,sum,now,lim);
}
int main() {
n=read();
for(ll i=1; i<=n; i++) {
a[i]=read();
}
dfs1(1,0,0,n/2);
dfs2(n/2+1,0,0,n);
for(ll i=1;i<=(1<<n);i++) ans+=v[i];
printf("%lld\n",ans);
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?
· 如何调用 DeepSeek 的自然语言处理 API 接口并集成到在线客服系统