[JOI 2022 Final] 自学 题解

发布时间 2023-07-26 15:18:31作者: linbaicheng2022

洛谷传送门

1.题意简述:

一个学期有 \(N\)\(N*M\) 节课,每天的第 \(i\) 节课可以选择效果 \(a_i\) 的学习与 \(b_i\) 的自习。问应如何安排每节课,使所有功课最小值最大?

2.思路:

这道题想直接模拟非常麻烦,但是如果我们能够灵活运用二分算法,这道题就非常简单了。

考虑到数据范围较大,我们可以在 \(0 - {10}^{18}\) 的范围内二分枚举答案,写一个 \(check\) 函数判断答案是否成立。

\(check\) 函数中,我们不妨枚举范围较小的天数 \((1 \leq n \leq 3e5)\) ,对于每天的 \(m\) 节课,我们采用贪心策略,即当自习效率高于上课效率时,我们优先使用自习。因为每天上课次数只有 \(m\) 次,而自习次数是无限的。。。

若上课效率更高,则分两种情况讨论:

  • 1.可以在 \(m\) 节课内达到二分枚举的答案,则全部上课;

  • 2.不可以在 \(m\) 节课内达到目标,则将 \(m\) 个机会全部用上,再将剩下效率通过自习完成。

最后如果通过上述情况实现后仍无法达到目标,则返回 \(false\),若 \(n\) 天后仍没有退出,返回 \(true\)

3.注意事项:

  • 1.在判断是否达到目标时,我们不必统计课程效率,可以统计达到目标最少所需的课程数,这样统计似乎更加简单。。。

  • 2.注意统计课程数时,因为需要用到除法的向上取整,而c++中的 \(ceil()\) 函数精度问题比较大,所以推荐使用不带 \(double\) 类型的向上取整函数:

int calc (int x, int y) {
	return x / y + (x % y != 0);
}

4.代码实现:

#include <bits/stdc++.h>
#define int long long

using namespace std;

#define N 300010
#define For(i,j,k) for(int i=j;i<=k;i++)
#define IOS ios::sync_with_stdio(),cin.tie(),cout.tie()

int n, m, a[N], b[N];

int calc (int x, int y) {
	return x / y + (x % y != 0);
}

bool check (int mid) {
	int day = 0;
	For (i, 1, n) {
		if (b[i] > a[i]) day += calc (mid, b[i]);
		else if (a[i] * m >= mid) day += calc (mid, a[i]);
		else day += m + calc (mid - a[i] * m, b[i]);
		if (day > n * m) return false;
	}
	return true;
}

signed main () {
	IOS;
	cin >> n >> m;
	
	For (i, 1, n) cin >> a[i];
	For (i, 1, n) cin >> b[i];
	
	int l = 0, r = LLONG_MAX;
	while (l <= r) {
		int mid = l + r >> 1;
		if (check (mid)) {
			l = mid + 1;
		} else {
			r = mid - 1;
		}
	}
	
	cout << l - 1 << endl;
	return 0;
}