二分算法习题汇总

发布时间 2023-10-29 21:46:09作者: hsy2093

一、复制书稿

题目描述

现在要把 \(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;
}