Meet in the middle
搜索是\(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\)有何弊端?
当搜索层数增加时,时间复杂度增加过快。
可不可以减少搜索层数,甚至降至一半?
当然可以。不然我这篇文章写什么
看网上两张很好的图就一目了然了。


于是我们从\(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;
}
即\(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;
}
Meet in the middle的更多相关文章
- Meet in the middle学习笔记
Meet in the middle(MITM) Tags:搜索 作业部落 评论地址 PPT中会讲的很详细 当搜索的各项互不影响(如共\(n\)个物品前\(n/2\)个物品选不选和后\(n/2\)个物 ...
- SPOJ4580 ABCDEF(meet in the middle)
题意 题目链接 Sol 发现abcdef是互不相关的 那么meet in the middle一下.先算出abc的,再算def的 注意d = 0的时候不合法(害我wa了两发..) #include&l ...
- codevs1735 方程的解数(meet in the middle)
题意 题目链接 Sol 把前一半放在左边,后一半放在右边 meet in the middle一波 统计答案的时候开始想的是hash,然而MLE了两个点 实际上只要排序之后双指针扫一遍就行了 #inc ...
- 【BZOJ4800】[Ceoi2015]Ice Hockey World Championship (meet in the middle)
[BZOJ4800][Ceoi2015]Ice Hockey World Championship (meet in the middle) 题面 BZOJ 洛谷 题解 裸题吧,顺手写一下... #i ...
- 【CF888E】Maximum Subsequence(meet in the middle)
[CF888E]Maximum Subsequence(meet in the middle) 题面 CF 洛谷 题解 把所有数分一下,然后\(meet\ in\ the\ middle\)做就好了. ...
- 【CF912E】Prime Game(meet in the middle)
[CF912E]Prime Game(meet in the middle) 题面 CF 懒得翻译了. 题解 一眼题. \(meet\ in\ the\ middle\)分别爆算所有可行的两组质数,然 ...
- CF888E Maximum Subsequence (Meet in the middle,贪心)
题目链接 Solution Meet in the middle. 考虑到 \(2^{35}\) 枚举会超时,于是分成两半枚举(尽量平均). 然后不能 \(n^2\) 去匹配,需要用到一点贪心: 将数 ...
- 【BZOJ4800】[Ceoi2015]Ice Hockey World Championship Meet in the Middle
[BZOJ4800][Ceoi2015]Ice Hockey World Championship Description 有n个物品,m块钱,给定每个物品的价格,求买物品的方案数. Input 第一 ...
- cogs 304. [NOI2001] 方程的解数(meet in the middle)
304. [NOI2001] 方程的解数 ★★☆ 输入文件:equation1.in 输出文件:equation1.out 简单对比时间限制:3 s 内存限制:64 MB 问题描述 已 ...
随机推荐
- Python 中Lambda 表达式 实例解析
Lambda 表达式 lambda表达式是一种简洁格式的函数.该表达式不是正常的函数结构,而是属于表达式的类型.而且它可以调用其它函数. 1.基本格式: lambda 参数,参数...:函数功能代码 ...
- spring学习 六 spring与mybatis整合
在mybatis学习中有两种配置文件 :全局配置文件,映射配置文件.mybatis和spring整合,其实就是把mybatis中的全局配置文件的配置内容都变成一个spring容器的一个bean,让sp ...
- JS高级-***Function- ***OOP
1. ***Function 作用域(scope): 什么是: 一个变量的使用范围 为什么: 避免函数内外的变量间互相影响 包括: 2种: 1. 全局作用域: window 保存着全局变量: 随处可用 ...
- MZOJ 1345 hero
一道宽搜模版题,可写错了两个地方的我只得了56(掩面痛哭) http://10.37.2.111/problem.php?id=1345 先看看正确的 #include <bits/stdc++ ...
- SHELL脚本取系统当前年月日问题 (去0)
1. #!/bin/bash tmonth=`date +%m`tyear=`date +%y`tday=`date +%d`day=`expr $tday + 0`month=`expr $tmon ...
- 特级教师总结的教育之33条(ZZ)
一位班主任发给家长的一则短信:“无论成绩好坏,请想想:每个孩子都是种子,只不过每个人的花期不同.有的花,一开始就灿烂绽放:有的花,需要漫长的等待.不要看着别人怒放了,自己的那棵还没有动静就着急,相信是 ...
- LINUX系统下MySQL 压力测试工具super smack
摘要:1.源文件下载地址:http://vegan.net/tony/supersmack/2.安装:注意在编译时,可以先把对应的libmysqlclient.so.*拷贝到/usr/lib3.测试: ...
- msyql 主从配置
vim /etc/mysql/my.cnf; # 以下部分一定要配置在[mysqld]后面 [mysqld] log-bin=mysql-bin server-id= //设置数据库服务器唯一ID,这 ...
- centos7 新增ip
1.进入network-scripts目录:cd /etc/sysconfig/network-scripts/ 2.复制ifcfg-eth0: cp ifcfg-eth0 ifcfg-eth0:0 ...
- 移动赋值运算符(c++11)
1.概念 1)移动赋值运算符是一个重载的赋值运算符,参数为自身类的右值引用,返回值自身类的左值引用,由于不抛出任何异常,用noexcept指定(如果定义在类的外面,那么定义也要用noexcept指定) ...