搜索是\(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;
}

SP4580 ABCDEF

即\(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的更多相关文章

  1. Meet in the middle学习笔记

    Meet in the middle(MITM) Tags:搜索 作业部落 评论地址 PPT中会讲的很详细 当搜索的各项互不影响(如共\(n\)个物品前\(n/2\)个物品选不选和后\(n/2\)个物 ...

  2. SPOJ4580 ABCDEF(meet in the middle)

    题意 题目链接 Sol 发现abcdef是互不相关的 那么meet in the middle一下.先算出abc的,再算def的 注意d = 0的时候不合法(害我wa了两发..) #include&l ...

  3. codevs1735 方程的解数(meet in the middle)

    题意 题目链接 Sol 把前一半放在左边,后一半放在右边 meet in the middle一波 统计答案的时候开始想的是hash,然而MLE了两个点 实际上只要排序之后双指针扫一遍就行了 #inc ...

  4. 【BZOJ4800】[Ceoi2015]Ice Hockey World Championship (meet in the middle)

    [BZOJ4800][Ceoi2015]Ice Hockey World Championship (meet in the middle) 题面 BZOJ 洛谷 题解 裸题吧,顺手写一下... #i ...

  5. 【CF888E】Maximum Subsequence(meet in the middle)

    [CF888E]Maximum Subsequence(meet in the middle) 题面 CF 洛谷 题解 把所有数分一下,然后\(meet\ in\ the\ middle\)做就好了. ...

  6. 【CF912E】Prime Game(meet in the middle)

    [CF912E]Prime Game(meet in the middle) 题面 CF 懒得翻译了. 题解 一眼题. \(meet\ in\ the\ middle\)分别爆算所有可行的两组质数,然 ...

  7. CF888E Maximum Subsequence (Meet in the middle,贪心)

    题目链接 Solution Meet in the middle. 考虑到 \(2^{35}\) 枚举会超时,于是分成两半枚举(尽量平均). 然后不能 \(n^2\) 去匹配,需要用到一点贪心: 将数 ...

  8. 【BZOJ4800】[Ceoi2015]Ice Hockey World Championship Meet in the Middle

    [BZOJ4800][Ceoi2015]Ice Hockey World Championship Description 有n个物品,m块钱,给定每个物品的价格,求买物品的方案数. Input 第一 ...

  9. cogs 304. [NOI2001] 方程的解数(meet in the middle)

    304. [NOI2001] 方程的解数 ★★☆   输入文件:equation1.in   输出文件:equation1.out   简单对比时间限制:3 s   内存限制:64 MB 问题描述 已 ...

随机推荐

  1. Python 中Lambda 表达式 实例解析

    Lambda 表达式 lambda表达式是一种简洁格式的函数.该表达式不是正常的函数结构,而是属于表达式的类型.而且它可以调用其它函数. 1.基本格式: lambda 参数,参数...:函数功能代码 ...

  2. spring学习 六 spring与mybatis整合

    在mybatis学习中有两种配置文件 :全局配置文件,映射配置文件.mybatis和spring整合,其实就是把mybatis中的全局配置文件的配置内容都变成一个spring容器的一个bean,让sp ...

  3. JS高级-***Function- ***OOP

    1. ***Function 作用域(scope): 什么是: 一个变量的使用范围 为什么: 避免函数内外的变量间互相影响 包括: 2种: 1. 全局作用域: window 保存着全局变量: 随处可用 ...

  4. MZOJ 1345 hero

    一道宽搜模版题,可写错了两个地方的我只得了56(掩面痛哭) http://10.37.2.111/problem.php?id=1345 先看看正确的 #include <bits/stdc++ ...

  5. SHELL脚本取系统当前年月日问题 (去0)

    1. #!/bin/bash tmonth=`date +%m`tyear=`date +%y`tday=`date +%d`day=`expr $tday + 0`month=`expr $tmon ...

  6. 特级教师总结的教育之33条(ZZ)

    一位班主任发给家长的一则短信:“无论成绩好坏,请想想:每个孩子都是种子,只不过每个人的花期不同.有的花,一开始就灿烂绽放:有的花,需要漫长的等待.不要看着别人怒放了,自己的那棵还没有动静就着急,相信是 ...

  7. LINUX系统下MySQL 压力测试工具super smack

    摘要:1.源文件下载地址:http://vegan.net/tony/supersmack/2.安装:注意在编译时,可以先把对应的libmysqlclient.so.*拷贝到/usr/lib3.测试: ...

  8. msyql 主从配置

    vim /etc/mysql/my.cnf; # 以下部分一定要配置在[mysqld]后面 [mysqld] log-bin=mysql-bin server-id= //设置数据库服务器唯一ID,这 ...

  9. centos7 新增ip

    1.进入network-scripts目录:cd /etc/sysconfig/network-scripts/ 2.复制ifcfg-eth0: cp ifcfg-eth0 ifcfg-eth0:0 ...

  10. 移动赋值运算符(c++11)

    1.概念 1)移动赋值运算符是一个重载的赋值运算符,参数为自身类的右值引用,返回值自身类的左值引用,由于不抛出任何异常,用noexcept指定(如果定义在类的外面,那么定义也要用noexcept指定) ...