《J. 最大缘》题解
本题为 2025牛客寒假算法基础集训营2 废题,原题目名称为《一起画最大的圆!》,预估难度 2000 分。至此,真相大白!
让我们尝试用最标准的办法解决这道问题。
步骤一:子问题转换
不妨设答案的三个点为
、
和
,那么,最小的完全包含这三个点的矩形,一定是给定矩形区域的一个子矩形,也即我们将原问题转化为以下子问题:
现在,他已经划定了一个矩形区域,左侧边界为 ,右侧边界为
,下侧边界为
,上侧边界为
。且满足
、
。
显然,将答案的三点全体平移、左右对称、上下对称,子矩形也会同步发生位置上的变化,但其形状永不改变,且恒为给定矩形区域的子矩形。
步骤二:理论暴力解
我们可以证明,仅通过上述这三个位置上的变动,一定存在一种方案,使得答案的其中一点恰好位于
。我们将以此为突破口解决本题:固定第一点为
,
暴力枚举第二个点,再
暴力枚举第三个点,随后计算绘制圆的半径,找到最大的一个构造方案。这样做的时间复杂度为
,理论上勉强可以通过,但是,由于求圆半径的函数常数较大,实际上是严格超时的。
步骤三:复杂度优化
我们知道,当三点共线时,可以看作是画出了一个半径为无穷大的圆(可以使用正弦定理
证明)所以,当给定的三个点越接近共线,绘制出的圆也就越大。
回到此前暴力的解法上,容易发现,在两个点确定直线的前提下,第三个点越贴近直线,构造出的方案越接近三点共线。记已经绘制出的直线方程为
,由于第三点一定位于整数坐标,所以,枚举
,求解直线交每一列的点
,那么只需要检查这个点上下的整数点
和
即可;同理,枚举并检查
后检查直线交每一行点的左右整数点。这样一来,我们可以将暴力枚举第三个点的复杂度优化到
。
最终复杂度为
。
参考代码
#include <bits/stdc++.h>
using namespace std;
#ifdef WIDA_WORK_SPACE
#include "debug.h"
#endif
using i64 = long long;
using f64 = long double;
f64 getCircleRadius(i64 x1, i64 y1, i64 x2, i64 y2, i64 x3, i64 y3) {
i64 e = 2 * (x2 - x1);
i64 f = 2 * (y2 - y1);
i64 g = x2 * x2 - x1 * x1 + y2 * y2 - y1 * y1;
i64 a = 2 * (x3 - x2);
i64 b = 2 * (y3 - y2);
i64 c = x3 * x3 - x2 * x2 + y3 * y3 - y2 * y2;
if (e * b - a * f == 0) return 0;
f64 X = 1.L * (g * b - c * f) / (e * b - a * f);
f64 Y = 1.L * (a * g - c * e) / (a * f - b * e);
f64 R2 = (X - x1) * (X - x1) + (Y - y1) * (Y - y1);
return R2;
}
signed main() {
int a, b, c, d;
cin >> a >> b >> c >> d;
f64 MaxR = 0;
array<int, 6> ans;
for (int i = a + 1; i <= b; i++) { // 注意从 +1 开始,因为共线时找不到第三个点
for (int j = c + 1; j <= d; j++) {
for (int x = a; x <= i; x++) { // 【对拍点其一】除2优化
int y = c + (j - c) * (x - a) / (i - a);
auto check = [&](int x, int y) {
f64 chk = getCircleRadius(a, c, i, j, x, y);
if (chk > MaxR) {
MaxR = chk;
ans = {a, c, i, j, x, y};
}
};
check(x, y);
check(x, min(j, y + 1));
check(x, max(c, y - 1));
}
}
}
for (int i = 0; i < 6; i++) {
cout << ans[i] << " \n"[i % 2];
}
}
int __OI_INIT__ = []() {
ios::sync_with_stdio(0), cin.tie(0);
cout.tie(0);
cout << fixed << setprecision(12);
return 0;
}();
本题还有一些与众不同的解法,比如,固定一点为左上角,随后在右下角
个点中枚举,同时第三点使用与上方相同的办法枚举最贴近连出的直线的点,可以将复杂度优化到
,经本人三天两夜数据全枚举,可以通过本题。可以说在当前的数据范围内这也是正解。顺带一提,如果范围是
,只需要枚举
个点。
另外还看到 KUN 哥的神秘 32ms 做法,没研究明白,先留个链接(留待读者自证)。
《K. 26》题解
要求的内容为标准键盘
键的相邻字母,打表输出即可(这就是为什么题目里有“默默敲键盘”)。
参考代码
#include <bits/stdc++.h>
using namespace std;
#ifdef WIDA_WORK_SPACE
#include "debug.h"
#endif
using i64 = long long;
signed main() {
char x;
cin >> x;
x -= 'A';
vector<string> adj = {
"QWSZ",
"VGHN",
"XDFV",
"ERSFXC",
"WRSD",
"RTDGCV",
"TYFHVB",
"YUGJBN",
"UOJK",
"UIHKNM",
"IOJLM",
"OPK",
"JKN",
"HJBM",
"IPKL",
"OL",
"WA",
"ETDF",
"WEADZX",
"RYFG",
"YIHJ",
"FGCB",
"QEAS",
"SDZC",
"TUGH",
"ASX",
};
cout << adj[x] << "\n";
}
int __OI_INIT__ = []() {
ios::sync_with_stdio(0), cin.tie(0);
cout.tie(0);
cout << fixed << setprecision(12);
return 0;
}();