ABC093D Worst Case
思路
二分答案。
首先确定答案的上界。
不妨设 \(A \le B\),若 \(x y < AB\) 则 $x ,y $ 中至少有一个要小于 \(B\),故两场比赛排名之积小于 \(AB\) 的人至多有 \(2(B - 1)\) 个。
问题转化为:如何判断是否可能有 \(n\) 个人两场比赛的排名的乘积小于 \(AB\)。
不难证明,最有利的情况是:不计 Takahashi,第一场比赛的前 \(n\) 名也是第二场比赛的前 \(n\) 名,并且在这 \(n\) 个人之中,第一场比赛排名第一的那个人第二场比赛排名第 \(n\),第一场比赛排名第二的那个人第二场比赛排名第 \(n - 1\),以此类推。举例言之,设 \(A = 2, B= 3, n = 5\),最有利的情况是这五个人两场比赛的排名分别为 \((1, 6), (3, 5), (4, 4), (5, 2), (6, 1)\)。问题化为:计算这种情况下名次乘积的最大值。这相当于求一个自变量是正整数的分段函数的最大值,这个函数最多有三段,每一段都是一个开口向下的二次函数,形如 \(x( s - x)\)。需要算出每一段的左右端点和每一段上 \(s\) 的值。
代码
int main() {
#if defined LOCAL && !defined DUIPAI
ifstream in("in.txt");
cin.rdbuf(in.rdbuf());
// ofstream out("main.out");
// cout.rdbuf(out.rdbuf());
#endif
auto get_max = [](int l, int r, int s) {
int cand1, cand2;
if (s % 2 == 0) {
cand1 = cand2 = s / 2;
} else {
cand1 = (s - 1) / 2;
cand2 = cand1 + 1;
}
if (l <= cand1 && cand1 <= r) {
return 1ll * cand1 * (s - cand1);
}
if (l <= cand2 && cand2 <= r) {
return 1ll * cand2 * (s - cand2);
}
return max(1ll * l * (s - l), 1ll * r * (s - r));
};
auto check = [&get_max](int a, int b, int n) {
ll max_v = 0;
vector<pii> x, y;
if (a > n) x.eb(1, n);
else {
if (a != 1) x.eb(1, a - 1);
x.eb(a + 1, n + 1);
}
if (b > n) y.eb(1, n);
else {
if (b != 1) y.eb(1, b - 1);
y.eb(b + 1, n + 1);
}
int i = 0, j = SZ(y) - 1;
while (i < SZ(x)) {
int t = min(x[i].second - x[i].first + 1, y[j].second - y[j].first + 1);
chkmax(max_v, get_max(x[i].first, x[i].first + t - 1, x[i].first + y[j].second));
x[i].first += t;
y[j].second -= t;
if (x[i].first > x[i].second) ++i;
if (y[j].first > y[j].second) --j;
}
return max_v;
};
int q;
scan(q);
rep (q) {
int a, b;
scan(a, b);
int l = 1, r = 2 * (max(a, b) - 1);
while (l <= r) {
int mid = get_mid(l, r);
if (check(a, b, mid) < 1ll * a * b) {
l = mid + 1;
} else {
r = mid - 1;
}
}
println(r);
}
#if defined LOCAL && !defined DUIPAI
cout << "Time elapsed: " << 1.0 * clock() / CLOCKS_PER_SEC << " s.\n";
#endif
return 0;
}