原题来自Google CodeJam 2017 Round C的第四题4M Corporation,大意如下:
给定四个正整数,求:
至少需要多少个数,才可以满足最小值为,最大值为,均值为,中位数为?
如果存在可行的解,给出最少需要的数的个数;如果不存在,则输出IMPOSSIBLE。
简单的无解情形
首先,我们可以简单地排除掉一些无解的情形:
这样,我们可以保证下式成立
简单的有解情形
在上面的条件下,我们很容易找到两种特殊情形:
- ,此时,只需要一个数{}
- ,此时,只需要两个数{}
一般情形
如果不满足上面的简单有解情形,我们至少需要3个数才有可能满足条件,也即
那么,接下来如何进行呢?
我们需要对一组数的、、、之间的关系进行更加深入的观察。
因为中位数在数的个数为奇数和偶数时有不同的表达形式,这启发我们对奇偶进行分别讨论。
奇数情形
先看奇数的情形。
我们把个数从小到大排列并求和,可以得到如下的关系式:
考虑大小关系,我们可以得到下面的不等式:
也即:
现在MIN、MAX、MEAN、MEDIAN都已知,这意味着我们可以直接求解这个关于的不等式,从而得到:
从而得到
我们把奇数情形下的解记为
注意,由于必定为正整数,当或时,不等式无解。
偶数情形
接下来,再考虑的情形,类似奇数的情形,我们可以得到:
进而得到不等式:
这里,为什么中间的两个数都设置为,而不是和,或者其他组合呢?
从上面的不等式,我们很容易看到,如果中间两个数设置成,则不等式左端会增大,而不等式右端会减小,这样就缩小了解的范围。
整理得到:
同样地,求解上面的不等式,我们可以得到:
不等式无解的条件与奇数情形一致。所以,或时,原问题无解(这里的前提是和时两种简单解均不成立)。
偶数情形下的解,我们记为
显然,最后的解,到这里,我们也就圆满地解决了上面的问题。
如果去掉题目中的正整数限制,这一解法同样成立。
思考
在上面解题的过程中,我们发现了或时会导致无解,那么反过来,我们就可以得到有解时,关于的不等式:
其中。
原问题有解,意味着这样的一组数是有可能存在的,而原问题无解,则说明这样的一组数不可能存在。这就意味着,我们得出的原问题有解的条件,应当是任意个满足的实数的固有性质。
下面我们从数学上证明和分别是的下确界和上确界。
我们还是分奇数和偶数两种情况来考虑这一问题。
奇数情形
令函数,其中
对求导,得到
因此单调递减。
接下来,我们需要求的极限,根据洛必达法则,易得。
由于
也就证明了就是的下确界。
同样,令
有
因此单调递增,并且。
由于
我们得到是的上确界。
偶数情形
证明方法同上。
总结
综合奇数和偶数的情形,我们也就证明了对于任意个数,当成立时,必然有
附:源程序
#include <algorithm>
#include <iostream>
#include <cmath>
using namespace std;
void solve(int case_num) {
int MIN, MAX, MEAN, MEDIAN;
int count{0};
cin >> MIN >> MAX >> MEAN >> MEDIAN;
// 简单的无解情形
if (
MIN > MAX ||
MEAN > MAX ||
MEAN < MIN ||
MEDIAN > MAX ||
MEDIAN < MIN
) {
cout << "Case #" << case_num << ": " << "IMPOSSIBLE" << endl;
return;
}
// 简单的有解情形
if (MIN == MAX) count = 1;
else if (MIN + MAX == 2 * MEAN && MEAN == MEDIAN) count = 2;
else {
// 不等式无解的情形
if (2 * MEAN >= MEDIAN + MAX || 2 * MEAN <= MIN + MEDIAN) {
cout << "Case #" << case_num << ": " << "IMPOSSIBLE" << endl;
return;
}
// 偶数情形
int left = ceil((double) (MAX - MIN) / (2 * MEAN - MIN - MEDIAN));
int right = ceil((double) (MAX - MIN) / (MAX + MEDIAN - 2 * MEAN));
int k = max(left, right);
int even = 2 * k;
// 奇数情形
left = ceil((double) (MAX - MEAN) / (2 * MEAN - MIN - MEDIAN));
right = ceil((double) (MEAN - MIN) / (MAX + MEDIAN - 2 * MEAN));
k = max(left, right);
int odd = 2 * k + 1;
count = min(even, odd);
}
cout << "Case #" << case_num << ": " << count << endl;
}
int main() {
int t;
cin >> t;
for (int i = 1; i <= t; ++i) {
solve(i);
}
return 0;
}