二分算法习题汇总
二分算法习题汇总
一、复制书稿
题目描述
现在要把 \(m\) 本有顺序的书分给 \(k\) 个人复制(抄写),每一个人的抄写速度都一样,一本书不允许给两个(或以上)的人抄写,分给每一个人的书,必须是连续的,比如不能把第一、第三、第四本书给同一个人抄写。
现在请你设计一种方案,使得复制时间最短。复制时间为抄写页数最多的人用去的时间。
输入格式
第一行两个整数 \(m,k\)。
第二行 \(m\) 个整数,第 \(i\) 个整数表示第 \(i\) 本书的页数。
输出格式
共1行:分配给抄写员的页数的最大值
样例 #1
样例输入 #1
9 3
1 2 3 4 5 6 7 8 9
样例输出 #1
17
提示
\(1\le k \le m \le 500\)。
代码实现
#include <stdio.h>
#include <iostream>
#include <math.h>
#include <algorithm>
#include <cstring>
using namespace std;
int M, K;
int page[505];
//判别答案合法性
bool check(int num){
int group = 1, rest = num;
for(int i = 0; i < M; i++){
//如果这个人当前可以抄的上限页数大于当前这本书书的页数,就交给他抄,不用新增人。
if(rest >= page[i]) rest -= page[i];
//如果这个人抄不了这本书,就要再来一个人,并且更新他的上限页数。
else{
group ++;
rest = num - page[i];
if(rest < 0)
return false;
}
}
//group表示最少需要多少个人
return group <= K;
}
int main(){
cin >> M >> K;
int sum = 0;
int l = 0, r, mid;
for(int i = 0; i < M; i++){
cin >> page[i];
sum += page[i];
}
r = sum;
while(l < r){
mid = (l + r) >> 1;
//如果mid合法,则在左区间寻找答案
if(check(mid))
r = mid;
else
l = mid + 1;
}
cout << l << endl;
}
二、 青蛙(frog)
题目描述
小 L 向一所小学捐赠了一些青蛙,这些青蛙一共有 \(M\) 种品种,每只青蛙都属于一种品种。
老师需要把所有的青蛙分给 \(N\) 个孩子。每个孩子得到的所有青蛙都必须属于相同的品种,而且可以有一些孩子一只青蛙也没得到。
我们把怄火值定义为得到青蛙最多的孩子得到的青蛙数量。请你帮助老师分青蛙,使得怄火值最小。
例如,如果有 \(4\) 只红品种的青蛙(\(\texttt{RRRR}\))和 \(7\) 个蓝品种的青蛙(\(\texttt{BBBBBBB}\)),要分给 \(5\) 个孩子,
那么我们可以这样划分:\(\texttt{RR}\),\(\texttt{RR}\),\(\texttt{BB}\),\(\texttt{BB}\),\(\texttt{BBB}\)。这样分的怄火值为 \(3\),是最小的。
输入格式
第一行两个正整数,\(N,M\),含义如题目所示。
第二行 \(M\) 个整数,表示第 \(M\) 个品种的青蛙有几只,保证每个品种的青蛙的数量都在 \([1,10^9]\) 中。
输出格式
一行一个整数,表示最小的怄火值。
样例 #1
样例输入 #1
7 4
1 2 3 4
样例输出 #1
2
提示
对于 \(20\%\) 的数据,保证 \(1 \le M \le 10\)。
对于另外 \(30\%\) 的数据,保证 \(1\le M\le 1000\),\(1\le N\le 10000\)。
对于 \(100\%\) 的数据,保证 \(1 \le M \le 3 \times 10^5\),\(1 \le N \le 10^9\),\(M \le N\)。
代码实现
#include <stdio.h>
#include <iostream>
using namespace std;
int a[300005];
int n, m;
bool check(int mid){
int cnt = 0;
for(int i = 1; i <= m; i++){
if(a[i] % mid == 0) cnt += (a[i]/mid);
else cnt += (a[i]/mid+1);
if(cnt > n) return false;
}
return true;
}
int main() {
int maxx = 0;
cin >> n >> m;
for(int i = 1; i <= m; i++){
cin >> a[i];
maxx = max(maxx, a[i]);
}
int l = 1, r = maxx;
while(l < r){
int mid = (l+r) / 2;
if(check(mid)) r = mid;
else l = mid + 1;
}
cout << r << endl;
}
三、平均数
题目描述
给一个长度为 \(n\) 的数列,我们需要找出该数列的一个子串,使得子串平均数最大化,并且子串长度 \(\ge m\)。
输入格式
第一行两个整数 \(n\) 和 \(m\)。
接下来 \(n\) 行,每行一个整数 \(a_i\),表示序列第 \(i\) 个数字。
输出格式
一个整数,表示最大平均数的 \(1000\) 倍,如果末尾有小数,直接舍去,不要用四舍五入求整。
样例 #1
样例输入 #1
10 6
6
4
2
10
3
8
5
9
4
1
样例输出 #1
6500
提示
数据规模与约定
- 对于 \(60\%\) 的数据,保证 \(m\le n\le 10^4\);
- 对于 \(100\%\) 的数据,保证 \(1 \leq m\le n\le 10^5\),\(0\le a_i\le2000\)。
代码实现
#include <stdio.h>
#include <iostream>
using namespace std;
long long a[100005], sum[100005];
long long n, m, maxx = 0;
bool check(long long mid){
long long minn = 1e15;
for(int i = 1; i <= n; i++){
sum[i] = sum[i-1] + a[i] - mid;
if(i >= m){
//可能为一个负值
minn = min(minn, sum[i-m]);
if(sum[i] >= minn) return true;
}
}
return false;
}
int main() {
cin >> n >> m;
for(int i = 1; i <= n; i++){
cin >> a[i];
a[i] *= 10000;
if(maxx < a[i]) maxx = a[i];
}
long long l = 0, r = maxx, mid;
while(l < r){
mid = (l+r+1) / 2;
if(check(mid)) l = mid;
else r = mid-1;
}
cout << l / 10 << endl;
return 0;
}
四、查询(query)
题目描述
青蛙老师有三个长度为 \(n\) 的数组 \(a,b,c\)。
给定一个正整数 \(k\),他想知道在所有 \(n^2\) 个二元组 \((i,j)(1\le i\le n,1\le j\le n)\) 中,\(a_j+b_j\times c_i\) 的第 \(k\) 小值是多少。
但是他不会做,于是将问题交给你了。
输入格式
第一行一个正整数 \(n\),表示数组长度。
第二行 \(n\) 个正整数,依次表示 \(a_1,a_2,\cdots a_n\)。
第三行 \(n\) 个正整数,依次表示 \(b_1,b_2,\cdots b_n\)。
第四行 \(n\) 个正整数,依次表示 \(c_1,c_2,\cdots c_n\)。
接下来一行,一个正整数 \(k\),含义如题所述。
输出格式
一行一个整数表示答案。
样例#1
样例输入#1
5
1 3 6 4 1
3 8 9 2 6
5 6 5 3 2
10
样例输出#1
16
数据范围
对于 \(16\%\) 的数据,满足 \(n\le 100,1\le a_i,b_i,c_i\le 1000\)。
对于另外 \(16\%\) 的数据,满足 \(n\le 1000\)。
对于另外 \(16\%\) 的数据,满足 \(k\le n,1\le a_i,b_i,c_i\le 1000\)。
对于另外 \(24\%\) 的数据,满足 \(k\le n\)。
对于 \(100\%\) 的数据,满足 \(n\le 10^5, 1\le k\le n^2, 1\le a_i,b_i,c_i\le 10^9\)。
代码实现
//query.cpp
#include <stdio.h>
#include <iostream>
#include <algorithm>
typedef long long ll;
using namespace std;
ll a[100005], b[100005], c[100005];
ll n, k;
bool check(ll x){
ll cnt = 0;
//当固定i时,有多少个c满足乘积结果小于x的情况
for(int i = 1; i <= n; i++){
ll l = 0, r = n;
while(l < r){
ll mid = (l + r + 1) / 2;
if(a[i] + b[i] * c[mid] >= x) r = mid-1;
else l = mid;
}
cnt += l;
}
if(cnt >= k) return true;
else return false;
}
int main(){
cin >> n;
for(int i = 1; i <= n; i++) cin >> a[i];
for(int i = 1; i <= n; i++) cin >> b[i];
for(int i = 1; i <= n; i++) cin >> c[i];
cin >> k;
sort(c+1, c+n+1);
ll l = 0, r = 1e19;
//固定i,找对应的j
while(l < r){
ll mid = (l + r + 1) / 2;
//check合法证明序列中有k个数或大于k个数小于mid,所以mid应该往更小的地方找
if(check(mid)) r = mid - 1;
//否则要往大了找
else l = mid;
}
cout << r << endl;
return 0;
}

浙公网安备 33010602011771号