代码源 二分答案

179 阅读2分钟

给一个序列 a1, a2 , … , an

你可以对这个序列进行操作,每次操作可以选择一个元素,把它加 1,经过不超过 k 次操作之后,希望序列里面的最小值最大。问这个值是多少。

输入格式

第一行两个整数 n, k。接下来一行 n 个整数,表示 a1, a2 , … , an

输出格式

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

样例输入1

5 10
1 4 2 6 8

样例输出1

5

样例输入2

1 10000000000000
100000000

样例输出2

10000100000000

数据规模

对于 100% 的数据,满足 1n105,1ai108,0k10131≤n≤10^5,1≤ai≤10^8,0≤k≤10^{13}

思路

二分法

题目要求序列中的最小值最大,因此我们可以使用二分查找来寻找最小值的最大值。

二分的区间为 [1, 10^14],也就是最小值的最大值的取值范围,因为 k 最大为 10^13ai 最大为 10^8,经过 k 次 + 1,最坏的情况下,最小值的最大值为 10^13 + 10^8 ,所以我们这里二分的区间右端点设置为 10^14

假设当前二分到的最小值为 x,那么我们需要统计将每个数都加到 x 需要的操作次数,并判断总操作次数是否小于等于 k。如果总操作次数小于等于 k,则说明当前最小值符合要求,因此我们将左边界更新为 x;否则,当前最小值不符合要求,因此我们将右边界更新为 x-1。

AC代码

#include <iostream>
#include <algorithm>
using namespace std;

const int N = 1e5 + 10;

int n;
long long k;
long long a[N];

bool check(long long x)   // check 函数用于判断当前最小值是否符合要求
{
    long long cnt = 0;
    for (int i = 0; i < n; i ++ )
    {
        if (a[i] < x)           // 如果当前数小于最小值,则需要进行操作
            cnt += x - a[i];    // 计算需要操作的次数
        if (cnt > k)            // 如果操作次数超过了 k,则说明当前最小值不符合要求
            return false;
    }
    return true;
}

int main()
{
    cin >> n >> k;
    for (int i = 0; i < n; i ++ ) cin >> a[i];

    long long l = 1, r = 1e14;
    while (l < r)
    {
        long long mid = (l + r + 1) >> 1;
        if (check(mid)) l = mid;   // 如果当前最小值符合要求,则更新 l
        else r = mid - 1;          // 否则更新 r
    }
    cout << l << endl;

    return 0;
}

暴力法

只过了1个测试用例,会出现 Time Limit Exceeded,所以还是得用上面的二分法 :)

#include <iostream>
#include <algorithm> 
#include <cstring>

using namespace std;

const int N = 1e5 + 10;
typedef long long LL;
int n;
LL m;
int a[N];


int main() {
	cin >> n >> m;
	for (int i = 0; i < n; i ++) {
		cin >> a[i];
	}
	while (m--) {
		if (*max_element(a, a + n) == *min_element(a, a + n)) {
			cout << *min_element(a, a + n) + m + 1 << endl;
			return 0;
		} 
		int min_index = min_element(a, a + n) - a;
		a[min_index]++;
	} 
	cout << *min_element(a, a + n) << endl;
	return 0;
}

题目来源:二分答案 - 题目 - Daimayuan Online Judge