二分基础
二分
(类似于单调函数求零点)
二分查找
- 在一个单调有序的集合中查找元素,每次将集合分为左右两部分,判断解在哪个部分中并调整集合上下界,重复直到找到目标元素
题目:
给定一串n个单调递增的数,有q次询问>=x且<=y的数有多少个
数据规模:1
手动实现
写法一
(注意下取整,避免死循环)
//找>=x的第一个位置 while(l < r){ mid = (l + r) / 2; if(a[mid] >= x) r = mid; else l = mid + 1; }
//找<=x的最后一个位置 while (l < r) { mid = (l + r + 1) / 2; //因为是下取整,所以要+1,避免出现l=l的情况 eg:3 3 3出现死循环 if(a[mid] <= x) l = mid; else r = mid - 1; }
写法二
(永远把
//找>=x的第一个位置 while (l <= r) { mid = (l + r) / 2; if(a[mid] >= x) r = mid - 1; //ans是最后一次a[mid]>=x成立的mid,所以ans=r+1也就是l else l = mid + 1; }
//找<=x的最后个位置 while (l <= r) { mid = (l + r) / 2; if(a[mid] <= x) l = mid + 1; //ans是最后一次a[mid]<=x成立的mid,所以ans=l-1 else r = mid - 1; }
Ending Edition
//找>=x的第一个位置 //最大值的最小值 while(l <= r) { mid = (l + r) / 2; if(a[mid] >= x){ r = mid - 1; ans = min(ans, mid); }else{ l = mid + 1; } }
//找<=x的最后一个位置 //最小值的最大值 while(l <= r) { mid = (l + r) / 2; if(a[mid] <= x){ l = mid + 1; ans = min(ans, mid); }else{ r = mid - 1; } }
C++STL的二分查找函数
binary_search
返回bool值,是否存在lower_bound
从已排好序的序列a中利用二分搜索找出指向满足 的 的最小的指针upper_bound
从已排好序的序列a中利用二分搜索找出指向满足 的 的最小的指针
如果想要知道下标,减去a即可
Eg:lower_bound(a, a + 11, 55);
USACO music notes
手写二分
#include<bits/stdc++.h> #define ios ios::sync_with_stdio(false),cin.tie(0),cout.tie(0) using namespace std; typedef long long LL; const int N = 5e4 + 10; int a[N], sum[N]; int n, q; int main() { ios; cin >> n >> q; for (int i = 1; i <= n; i++) { cin >> a[i]; sum[i] = sum[i - 1] + a[i]; } while (q--) { int x; cin >> x; x++; int l = 1, r = n; while (l <= r) { int mid = (l + r) / 2; if (sum[mid] >= x) r = mid - 1; else l = mid + 1; } cout << l << "\n"; } return 0; }
STL
#include<bits/stdc++.h> #define ios ios::sync_with_stdio(false),cin.tie(0),cout.tie(0) using namespace std; typedef long long LL; const int N = 5e4 + 10; int a[N], sum[N]; int n, q; int main() { ios; cin >> n >> q; for (int i = 1; i <= n; i++) { cin >> a[i]; sum[i] = sum[i - 1] + a[i]; } while (q--) { int x; cin >> x; x++; int ans = lower_bound(sum + 1, sum + n + 1, x) - sum; cout << ans << "\n"; } return 0; }
二分答案+检验
二分枚举+检验: 将求解类问题转化为验证类问题
牛棚
题目描述:
有N个牛棚在x轴上,已知他们的坐标。有C只奶牛,每只都必须安排在一个牛棚里,一个牛棚只能容纳一只,但是他们会互相攻击,所以要求距离最近的两个牛棚间的距离最大。
(2 <= N <= 100000, 0 <=
分析:
- 我们可以假设距离最近的两个牛棚间的距离为x,判断这个x是否可行
- 先把
排序 - 对于一个解m,我们验证m是否可行
- 第一个点放置牛,从左向右
扫描一遍牛棚,第 个点如果和上一个放置点的距离 m,则在第 个点放置一头牛,统计总放置的牛数量是否 C
#include<bits/stdc++.h> #define ios ios::sync_with_stdio(false),cin.tie(0),cout.tie(0) using namespace std; typedef long long LL; const int N = 1e5 + 10; LL a[N]; int n, c; bool check(int x) { int la = a[1]; int cnt = 1; for (int i = 2; i <= n; i++) { if (a[i] - la >= x) { la = a[i]; cnt++; } if (cnt >= c) return true; } return false; } int main() { ios; while (scanf("%d%d", &n, &c) != EOF) { for (int i = 1; i <= n; i++) { scanf("%lld", & a[i]); } sort(a + 1, a + n + 1); int mid, l = 1, r = a[n]; while (l <= r) { mid = (l + r) >> 1; if (check(mid)) l = mid + 1; else r = mid - 1; } cout << r << endl; } return 0; }
Drying(poj)
题目描述:
有n件衣服需要晾干,每件含水量
- 1
- 1
- 1
分析: - 设某次二分出的一个值是mid
- 对于一件
衣服,直接晾干即可; - 对于一件
的衣服,最少的用时是用吹风机一段时间,晾干一段时间,设这两段时间分别是 和 ,那么有 , ,解得 ,所以对 向上取整就是该件衣服的最少用时。 - 我们把所有的使用吹风机时间加起来,这个总和应该
(太毒瘤了这题)
//poj不能用万能头 #include <iostream> #include <cstdio> #include <algorithm> #include <cmath> using namespace std; typedef long long LL; const int N = 1e5 + 10; int a[N]; int n, k; bool judge(int x) { LL sum = 0; for (int i = 1; i <= n; i++) { if (a[i] <= x) continue; sum += ceil((a[i] - x) * 1.0 / (k - 1)); //上取整, 毒瘤点:1.吹的同时也在自然晾干 2.k=1时除0 } return sum <= x; } int main() { scanf("%d", &n); int l = 1, r = 0; for (int i = 1; i <= n; i++) { scanf("%d", &a[i]); r = max(r, a[i]); } cin >> k; if (k == 1) { printf("%d", r); return 0; } while (l <= r) { int mid = (l + r) / 2; if (judge(mid))r = mid - 1; else l = mid + 1; } printf("%d", r + 1); return 0; }
Chocolate Eating(USACO)
分析:
- 二分最小的快乐值
- 如果今天心情达不到快乐值,就吃巧克力达到为止
- 巧克力不够则目标值过大
4 Values whose Sum is 0(poj)
分析:
既然有四列,那么我们可以分别计算前两列和后两列的和(只需要
#include<bits/stdc++.h> #define ios ios::sync_with_stdio(false),cin.tie(0),cout.tie(0) using namespace std; typedef long long LL; const int N = 4010; int n, a[6][N]; int p[N * N], q[N * N]; void pre(int p[], int x, int y) { int cnt = 0; for (int i = 1; i <= n; i++) { for (int j = 1; j <= n; j++) { p[++cnt] = a[x][i] + a[y][j]; } } } int main() { ios; cin >> n; for (int i = 1; i <= n; i++) { cin >> a[1][i] >> a[2][i] >> a[3][i] >> a[4][i]; } pre(p, 1, 2); pre(q, 3, 4); n = n * n; sort(q + 1, q + 1 + n); LL cnt = 0; for (int i = 1; i <= n; i++) { cnt += upper_bound(q + 1, q + 1 + n, -p[i]) - lower_bound(q + 1, q + 1 + n, -p[i]); } cout << cnt << endl; return 0; }
扑克牌
分析:
直接贪心很难构造,又因为是满足单调性的(套数
- 如果当前待验证的数字
(即判断当前手上的牌够不够组成x套),首先,张数 的牌只需要每一套用一张就行, 的牌差几张就需要补几张 ,于是我们可以计算出究竟要补几张 。- 如果最后需要补的
数比手上有的 数多,说明 不够,不行。 - 如果最后需要补的
数比 还大,说明必然有两个 在一套牌里,也不行,其他情况都是可以的。
- 如果最后需要补的
借教室
分析:
二分第一个需要修改的申请人是第几个,然后用差分维护出来前面的这些人申请完了之后每天剩下多少个教室,如果有负的,说明之前已经有订单无法满足了,减少右界,否则增大左界。
#include<bits/stdc++.h> #define ios ios::sync_with_stdio(false),cin.tie(0),cout.tie(0) using namespace std; typedef long long LL; const int N = 1e6 + 10; int n, m; int r[N]; struct ty { int d, s, j; } a[N]; LL delta[N]; bool check (int x) { for (int i = 1; i <= n; i++) { delta[i] = r[i] - r[i - 1]; } for (int i = 1; i <= x; i++) { delta[a[i].s] -= a[i].d; delta[a[i].j + 1] += a[i].d; } LL sum = 0; for (int i = 1; i <= n; i++) { sum += delta[i]; if (sum < 0) { return 0; } } return 1; } int main() { ios; cin >> n >> m; for (int i = 1; i <= n; i++) { cin >> r[i]; } for (int i = 1; i <= m; i++) { cin >> a[i].d >> a[i].s >> a[i].j; } int l = 0, r = m; while (l <= r) { int mid = (l + r) / 2; if (check(mid)) l = mid + 1; //l-1最后一次能借,所以不能借是l else r = mid - 1; } if (l > m) cout << 0; //完成m件订单后l不是=m,而是大于m,因为l-1是最后一次能借,l-1等于m else cout << "-1" << endl << l << endl; return 0; }
总结
解决最大值最小or最小值最大的常见方法
- 贪心
- 二分
- 动态规划
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 零经验选手,Compose 一天开发一款小游戏!
· 一起来玩mcp_server_sqlite,让AI帮你做增删改查!!