Skip to content

F. Circle Perimeter - 题解

比赛与标签

比赛: Codeforces Round 944 (Div. 4) 标签: binary search, brute force, dfs and similar, geometry, implementation, math 难度: *1600

题目大意喵~

主人你好呀~!这道题是想让我们当一回小小数学家呢,喵!

题目会给我们一个整数半径 r,然后需要我们找出所有坐标是整数的点 (x, y)(也就是格点),这些点到原点 (0, 0) 的距离 d 必须满足 r <= d < r+1。最后,我们要数一数一共有多少个这样的点,的说。

简单来说,就是在坐标系上画两个同心圆,一个半径是 r,另一个半径是 r+1。我们要找的就是落在这两个圆之间(包括内圈,但不包括外圈)的整数坐标点有多少个,呐。

用数学语言来描述,就是找到满足下面这个不等式的整数对 (x, y) 的数量: r <= sqrt(x^2 + y^2) < r+1

解题思路,一起攻克它!

看到 sqrt (开方) 是不是有点头大呀?没关系,猫娘有办法让它变简单!

第一步:甩掉烦人的根号!

不等式两边都是正数,我们可以对整个不等式进行平方,这样既能去掉根号,又不会改变大小关系,计算起来也更精确,喵~ r^2 <= x^2 + y^2 < (r+1)^2 看,这样是不是清爽多啦?我们的问题就变成了:寻找有多少对整数 (x, y) 满足这个关于平方和的不等式。

第二步:对称大法好!

观察一下 x^2 + y^2 这个式子,是不是超级对称?如果 (x, y) 是一个解,那么 (-x, y), (x, -y), (-x, -y) 肯定也满足条件!而且,如果 xy 不相等,那么 (y, x) 也是一个解。

利用这个完美的对称性,我们可以大大简化问题!我们只用数出第一象限(x > 0, y > 0)里有多少个点,然后把结果乘以 4。最后再单独处理坐标轴上的点就大功告成啦!

第三步:坐标轴上的点点~

先来处理最简单的部分——坐标轴上的点。

  1. x = 0 时,不等式变为 r^2 <= y^2 < (r+1)^2。因为 y 是整数,所以 |y| 只能等于 r。这样我们就找到了两个点:(0, r)(0, -r)
  2. y = 0 时,同理,x 只能是 r-r。这样又找到了两个点:(r, 0)(-r, 0)

所以,在坐标轴上,我们稳稳地拿到了 4 个点,喵!

第四步:第一象限的双指针魔法!

现在是核心部分啦!我们要计算在 x > 0, y > 0 的范围内有多少个满足条件的点。

我们可以固定 x,然后去找合法的 y 有多少个。x 的取值范围是什么呢?因为 x^2 + y^2 >= r^2y > 0,所以 x 最大不会超过 r(如果 x > rx^2 就已经大于 r^2 了)。所以,我们只需要遍历 x1r 就可以了。

对于每一个固定的 x,我们需要找到有多少个正整数 y 满足: r^2 - x^2 <= y^2 < (r+1)^2 - x^2

一个一个 y 去试太慢啦!这里有一个超级厉害的技巧——双指针

  • 我们定义 y_outer,表示对于当前的 x,满足 x^2 + y^2 < (r+1)^2 的最大正整数 y
  • 我们定义 y_inner,表示对于当前的 x,满足 x^2 + y^2 < r^2 的最大正整数 y

那么,对于这个 x,所有合法的 y 的数量就是 y_outer - y_inner

最神奇的地方来啦:当我们把 x1 慢慢增加到 r 时,y_outery_inner 的值是不会增加的,只会保持不变或者减小。所以,我们根本不需要为每个 x 都重新计算 y 的范围。我们可以初始化 y_outery_inner (例如对于 x=1 的情况),然后在 x 增加的循环里,不断地让 y_outery_inner “向下滑动”到正确的位置。这个方法是不是很优雅,喵~

第五步:汇总答案!

最后,我们把所有部分加起来: 总点数 = 坐标轴上的点数 + 4 * (第一象限的点数)总点数 = 4 + 4 * quadrant_points

这样,我们就高效地解决了问题啦!

代码实现喵~

cpp
#include <iostream>

void solve() {
    long long r;
    std::cin >> r;

    // 我们需要找到整数点 (x, y) 满足
    // r <= sqrt(x^2 + y^2) < r + 1
    // 两边平方得到: r^2 <= x^2 + y^2 < (r+1)^2
    // 使用 long long 防止 r*r 溢出,因为 r 最大是 10^5
    long long r_sq = r * r;
    long long r_plus_1_sq = (r + 1) * (r + 1);

    // quadrant_points 用于统计第一象限 (x > 0, y > 0) 的点数
    long long quadrant_points = 0;

    // 这里使用双指针技巧来优化计算
    // 当 x 增加时, 满足条件的 y 的上界只会减小或不变
    // y_outer: 对于当前 x, 满足 x^2 + y^2 < (r+1)^2 的最大 y
    long long y_outer = r;
    // y_inner: 对于当前 x, 满足 x^2 + y^2 < r^2 的最大 y
    long long y_inner = r - 1;

    // 遍历第一象限的 x 轴,x 的范围是 [1, r]
    for (long long x = 1; x <= r; ++x) {
        long long x_sq = x * x;

        // 向下调整 y_outer 指针
        // 找到第一个使得 x^2 + y_outer^2 < (r+1)^2 的 y_outer
        while (y_outer > 0 && x_sq + y_outer * y_outer >= r_plus_1_sq) {
            y_outer--;
        }

        // 向下调整 y_inner 指针
        // 找到第一个使得 x^2 + y_inner^2 < r^2 的 y_inner
        while (y_inner > 0 && x_sq + y_inner * y_inner >= r_sq) {
            y_inner--;
        }

        // 对于当前的 x, 合法的 y 的数量就是 y_outer - y_inner
        quadrant_points += (y_outer - y_inner);
    }

    // 总点数 = 坐标轴上的4个点 + 4 * 第一象限的点数
    // 坐标轴上的点是 (r, 0), (-r, 0), (0, r), (0, -r)
    long long total_points = 4 + 4 * quadrant_points;

    std::cout << total_points << std::endl;
}

int main() {
    // 加速输入输出,喵~
    std::ios_base::sync_with_stdio(false);
    std::cin.tie(NULL);

    int t;
    std::cin >> t;
    while (t--) {
        solve();
    }

    return 0;
}

复杂度分析

  • 时间复杂度: O(r) 的说。 主循环从 x=1 遍历到 r,看起来是 O(r)。循环内部的两个 while 循环呢?虽然它们看起来像嵌套循环,但指针 y_outery_inner 在整个 for 循环的生命周期里,都只会单调递减。y_outer 总共最多从 r 减到 0y_inner 也是。所以 while 循环的总执行次数是 O(r) 级别的。因此,总的时间复杂度是 O(r)。考虑到所有测试用例的 r 之和不超过 10^5,这个速度是飞快的!

  • 空间复杂度: O(1) 的说。 我们只用了几个变量来存储半径、坐标和计数值,没有用到随 r 增大的额外空间,所以空间复杂度是常数级别,非常优秀,喵~!

知识点与总结

这道题真是一次有趣的几何与算法结合之旅呀!

  1. 问题转化: 核心思想是把一个几何问题(点到原点的距离)转化为代数不等式 r^2 <= x^2 + y^2 < (r+1)^2,这让我们能够完全在整数域内进行精确计算。
  2. 对称性利用: 看到 x^2, y^2 这种形式,就要立刻想到对称性!将问题分解到第一象限处理,再乘以4加上坐标轴上的点,是解决这类问题的经典思路。
  3. 双指针/尺取法: 这道题最亮眼的技巧就是双指针!它巧妙地利用了 yx 增大而单调不增的特性,将内层查找的复杂度从 O(log r) (二分) 或 O(r) (暴力) 优化到了均摊 O(1),使得整体复杂度达到线性的 O(r)
  4. 编程细节: 一定要注意使用 long long 来防止 r*r 这样的计算导致整数溢出哦!

希望这篇题解能帮到你,主人!以后遇到类似的题目,也要记得从这几个角度去思考哦!继续加油,你超棒的,喵~!

Released under the MIT License.