折半搜索笔记

devtools/2025/3/6 2:33:26/

前言

01 01 01 爆搜的时间复杂度通常为 O ( 2 n ) O(2^n) O(2n),只能应付 N N N 20 20 20 左右的题目,但是折半搜索可以应付 N N N 30 30 30 ~ 40 40 40 的题目。

思想

N N N 个数分为前后两半,先搜索前一半的状态,再搜索后一半的状态,分别记录两边状态能贡献的结果,后续就可以枚举前一半的贡献结果,再在右半结果二分查找,因此搜索的时间复杂度降到 O ( 2 × 2 2 n ) O(2\times 2^{\frac{2}{n}}) O(2×2n2)

例题

P4799 [CEOI 2015] 世界冰球锦标赛 (Day2)

题目描述

给出 Bobek 的预算和每场比赛的票价,试求:如果总票价不超过预算,他有多少种观赛方案。

如果存在以其中一种方案观看某场比赛而另一种方案不观看,则认为这两种方案不同。

输入格式

第一行,两个正整数 N N N M ( 1 ≤ N ≤ 40 , 1 ≤ M ≤ 1 0 18 ) M(1\leq N\leq 40,1\leq M\leq 10^{18}) M(1N40,1M1018),表示比赛的个数和 Bobek 那家徒四壁的财产。

第二行, N N N 个以空格分隔的正整数,均不超过 1 0 16 10^{16} 1016,代表每场比赛门票的价格。

输入格式

输出一行,表示方案的个数。由于 N N N 十分大,注意:答案 ≤ 2 40 \leq 2^{40} 240

输入样例

5 1000
100 1500 500 500 1000

输出样例

8

思路

因为 N N N 最大能到达 40 40 40,因此直接 01 01 01 爆搜,时间复杂度将达到 O ( 2 40 ) O(2^{40}) O(240),必然会超时,但是如果我们能降到 O ( 2 20 ) O(2^{20}) O(220) 左右,那么就可以接受,因此启发我们可以使用折半搜索,先将左右两边的结果用两个 vector 分别保存下来,将右 vector 从小到大排序,然后枚举左半边,在右 vector 中二分查找两者相加小于等于 M M M 的最右下标 p o s pos pos,那么对答案的贡献就是 p o s + 1 pos+1 pos+1

代码实现

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int n;
ll m, a[45];
vector<ll> lw, rw;//分别记录左右两边能提供的结果
void dfs1(int u, ll s) {//暴搜左半if (s > m) return;if (u == n / 2 + 1) {lw.push_back(s);return;}dfs1(u + 1, s);//不选dfs1(u + 1, s + a[u]);//选
}
void dfs2(int u, ll s) {//暴搜右半if (s > m) return;if (u == n + 1) {rw.push_back(s);return;}dfs2(u + 1, s);dfs2(u + 1, s + a[u]);
}
void solve()
{scanf("%d%lld", &n, &m);for (int i = 1; i <= n; i++) scanf("%lld", &a[i]);dfs1(1, 0);//左半边dfs2(n / 2 + 1, 0);//右半边ll ans = 0;sort(rw.begin(), rw.end());//为二分排序for (int i = 0; i < lw.size(); i++) {//枚举左半边int l = 0, r = rw.size() - 1, pos = -1;while (l <= r) {int mid = l + r >> 1;if (lw[i] + rw[mid] <= m) pos = mid, l = mid + 1;else r = mid - 1;}ans += pos + 1;}printf("%lld\n", ans);
}
int main()
{int t = 1;//scanf("%d",&t);while (t--) solve();return 0;
}

P9234 [蓝桥杯 2023 省 A] 买瓜

题目描述

小蓝正在一个瓜摊上买瓜。瓜摊上共有 n n n 个瓜,每个瓜的重量为 A i A_i Ai。小蓝刀功了得,他可以把任何瓜劈成完全等重的两份,不过每个瓜只能劈一刀。

小蓝希望买到的瓜的重量的和恰好为 m m m

请问小蓝至少要劈多少个瓜才能买到重量恰好为 m m m 的瓜。如果无论怎样小蓝都无法得到总重恰好为 m m m 的瓜,请输出 − 1 −1 1

输入格式

输入的第一行包含两个整数 n , m n,m n,m,用一个空格分隔,分别表示瓜的个数和小蓝想买到的瓜的总重量。

第二行包含 n n n 个整数 A i A_i Ai,相邻整数之间使用一个空格分隔,分别表示每个瓜的重量。

输入格式

输出一行包含一个整数表示答案。

输入样例

3 10
1 3 13

输出样例

2

思路

类似上题,左右暴力搜索之后,问题就相当于转化为问左右半各选一个数,使得和恰好等于 m m m,劈瓜次数最少的次数。

对于这种恰好等于某个数,我们就不用存下来再去二分查找,可以搜索左半边的时候直接用 m a p map map 来记录下(重量,劈瓜的次数),这样搜索右边的时候,对于 x x x,可以直接访问 m p [ m − x ] mp[m-x] mp[mx],跟答案取名即可。

注意点:

  • 因为考虑到奇数除以二会有浮点数,我们优先把重量和 m m m 全部乘 2 2 2
  • 因为有些重量,劈瓜 0 0 0 次就可以得到,因此不能直接用 m p [ m − x ] mp[m-x] mp[mx] 来判断有没有存在 m − x m-x mx 这个重量,而应该用 m p . c o u n t ( m − x ) mp.count(m-x) mp.count(mx)
#include <bits/stdc++.h>
using namespace std;
int main() {map<int, int> mp;mp[3] = 0;cout << mp[3] << " " << mp.count(3);//会输出0 1return 0;
}

代码实现

#include <bits/stdc++.h>
using namespace std;
int n, m, a[35], ans = 1e9;
unordered_map<int, int> mp;//用于记录左半(结果,所需劈瓜的次数)
void dfs1(int u, int s, int cnt) {if (s > m || cnt >= ans) return; //剪枝if (s == m) ans = min(ans, cnt);if (u == n / 2 + 1) {if (!mp.count(s)) mp[s] = cnt;//如果是第一次得到该重量,记录所需劈瓜次数else mp[s] = min(mp[s], cnt);//否则,选取劈瓜次数少的return;}dfs1(u + 1, s, cnt);dfs1(u + 1, s + a[u], cnt);dfs1(u + 1, s + a[u] / 2, cnt + 1);
}
void dfs2(int u, int s, int cnt) {//搜索右半边if (s > m || cnt >= ans) return;if (s == m) ans = min(ans, cnt);if (u == n + 1) {if (mp.count(m - s)) ans = min(ans, mp[m - s] + cnt);//如果m-s存在,跟答案取minreturn;}dfs2(u + 1, s, cnt);dfs2(u + 1, s + a[u], cnt);dfs2(u + 1, s + a[u] / 2, cnt + 1);
}
void solve()
{scanf("%d%d", &n, &m);m *= 2;//防止精度问题,可以全部先乘2for (int i = 1; i <= n; i++) scanf("%d", &a[i]), a[i] *= 2;sort(a + 1, a + n + 1);dfs1(1, 0, 0);dfs2(n / 2 + 1, 0, 0);if (ans == 1e9) ans = -1;printf("%d\n", ans);
}
int main()
{int t = 1;//scanf("%d",&t);while (t--) solve();return 0;
}

简单集合之和

题目描述

给了你一个长度为 n n n 的序列: a 1 , a 2 , . . . , a n a_1,a_2,...,a_n a1,a2,...,an

现在你需要从中选出任意个数,其中每个数最多选 1 1 1 次,并且组成一个集合,同时需要你找出对 x x x 取模后最大的集合之和。

现在请你输出该集合之和对 x x x 取余后的结果。

输入格式

第一行有两个正整数 n ( ≤ 35 ) n(\leq35) n(35) x ( 1 0 9 ) x(10^9) x(109),意义如题。

第二行有 n n n 个正整数: a 1 , a 2 , . . . , a n ( a i ≤ 1 0 9 ) a_1,a_2,... ,a_n(a_i\leq 10^9) a1,a2,... ,an(ai109)

输出格式

输出一个整数,表示所选子序列之和对 x x x 取模后的结果。

输入样例

3 20
199 41 299

输出样例

19

思路

折半搜索左右两半,过程中可以直接取模 x x x,将结果存下来,问题就转化为了两个集合中各取一个数,使得相加的和取模 x x x 的值最大。

对于这个新的问题,我们知道每个数的值在 [ 0 , x − 1 ] [0,x-1] [0,x1],枚举左半边的值 w w w,那么我们贪心地肯定希望有右半边有 x − 1 − w x-1-w x1w,可证明对于 x x x,最大值总是 x x x x + ( x+( x+(小于等于 x − 1 − w x-1-w x1w 的最大数 ) ) )其中一个,所以我们把右半边从小到大排序,每次二分小于等于 x − 1 − w x-1-w x1w 的最右位置,跟答案取 max 即可。

代码实现

#include <bits/stdc++.h>
using namespace std;
int n, x, a[40];
vector<int> lw, rw;//分别保存左右搜索能得到的值
void dfs1(int u, int s) {if (u == n / 2 + 1) {lw.push_back(s);return;}dfs1(u + 1, s);//不选dfs1(u + 1, (s + a[u]) % x);//选
}
void dfs2(int u, int s) {if (u == n + 1) {rw.push_back(s);return;}dfs2(u + 1, s);dfs2(u + 1, (s + a[u]) % x);
}
void solve()
{scanf("%d%d", &n, &x);for (int i = 1; i <= n; i++) scanf("%d", &a[i]);dfs1(1, 0), dfs2(n / 2 + 1, 0);sort(rw.begin(), rw.end());//为后续二分排序int ans = 0;for (int i = 0; i < lw.size(); i++) {//枚举左半边的值int l = 0, r = rw.size() - 1, pos = -1;ans = max(ans, lw[i]);while (l <= r) {int mid = l + r >> 1;if (rw[mid] <= x - 1 - lw[i]) pos = mid, l = mid + 1;else r = mid - 1;}if (pos != -1) ans = max(ans, lw[i] + rw[pos]);}printf("%d\n", ans);
}
int main()
{int t = 1;//scanf("%d",&t);while (t--) solve();return 0;
}

http://www.ppmy.cn/devtools/164010.html

相关文章

[IP] DDR_FIFO(DDR3 用户FIFO接口)

IP(DDR_FIFO)将DDR3 IP的用户侧复杂接口修改为简易的FIFO接口&#xff0c;用户侧更加简易例化使用MIG 核 IP介绍 c0_xx (连接DDR app接口) 此IP 仅需根据MIG配置进行有限修改&#xff0c;即可使用&#xff01; 关于IP详细使用说明&#xff0c;参考IP datasheet&#xff01; 示…

Rocky Linux 8.5 6G内存 静默模式(没图形界面)安装Oracle 19C

Oracle19c 下载地址 Database Software Downloads | Oraclehttps://www.oracle.com/database/technologies/oracle-database-software-downloads.html#db_ee 目录 一、准备服务器 1、服务器可以克隆、自己装 2、修改主机名 3、重启 4、关闭selinux 5、关闭防火墙 5.1、…

【PostgreSQL】如何对PostgreSQL数据库进行数据迁移

如何对PostgreSQL数据库进行数据迁移 使用 pg_dump 和 pg_restore 工具备份恢复 直接备份 PostgreSQL 数据目录备份恢复 使用 Docker 卷备份备份步骤恢复 总结 在使用 Docker 部署 PostgreSQL 数据库时&#xff0c;数据备份和恢复是非常重要的操作。以下是几种可靠的备份和恢复…

文生图开源模型发展史(2014-2025年)

文生图开源模型的发展历程是一段充满技术革新、社区生态繁荣与商业化竞争的多维度演进史。 一、技术萌芽期&#xff08;2014-2020年&#xff09; 核心突破 2014年&#xff1a;GAN&#xff08;生成对抗网络&#xff09;诞生&#xff0c;首次实现数据驱动式图像生成&#xff0…

基因型—环境两向表数据分析——基因型评价

参考资料&#xff1a;农作物品种试验数据管理和分析 1、广适性基因型鉴定 基因型评价的目的是鉴定出具有广泛适应性或特殊适应性的基因型。鉴别广泛适应性的基因型&#xff0c;应当使用包括所有试点的GGE双标图“平均值与稳定性”功能图。在解释该功能图之前&#xff0c;需要先…

《Android-RecyclerView实现封面滑动到指定位置放大》---ViewPager封面指示器

一、实现效果 二、关键代码 1、自定义:LinearLayoutManager 指定位置放大item import android.content.Context; import android.util.DisplayMetrics; import android.view.View; import android.view.ViewGroup;import androidx.recyclerview.widget.LinearLayoutManager;…

linux中断调用流程(arm)

文章目录 ARM架构下Linux中断处理全流程解析&#xff1a;从硬件触发到驱动调用 ⚡**一、中断触发与硬件层响应** &#x1f50c;**1. 设备触发中断** &#x1f4e1; **二、CPU阶段&#xff1a;异常入口与上下文处理** &#x1f5a5;️**1. 异常模式切换** &#x1f504;**2. 跳转…

现代前端框架渲染机制深度解析:虚拟DOM到编译时优化

引言&#xff1a;前端框架的性能进化论 TikTok Web将React 18迁移至Vue 3后&#xff0c;点击响应延迟降低42%&#xff0c;内存占用减少35%。Shopify采用Svelte重构核心交互模块&#xff0c;首帧渲染速度提升580%。Discord在Next.js 14中启用React Server Components后&#xf…