L. 分煎饼

知识点:计算几何、二分

直接计算能平分所有圆的直线的方程比较困难,但是给定一条直线,分别计算两侧的圆的面积和是相对容易的。

一条直线可以用两个参数确定,考虑固定住直线的一个参数,在调整另一个参数的过程中,如果直线两边的面积是单调变化的,就可以使用二分来解决这个问题。

举个例子,可以固定斜率为零,二分截距。

当然还有很多种二分的方法,出题人试过以下几种,都轻松通过了,没有出现精度问题。

  • 固定方向为水平或垂直,二分与原点的距离
  • 随机一个方向,二分与原点的距离
  • 固定经过原点,二分倾斜角

考虑如何根据一条给定的直线计算一个圆在直线两侧的面积。

alt

如图所示,设圆的半径为 ,圆心距直线的距离为 (有正负)。

如果 ,则两侧的面积很好计算。

否则,直线上方的面积可以用扇形 ACD 的面积(记为 )减去三角形 ACD 的面积(记为 )表示。

alt

则有 alt (注意: 是有正负的)

alt

alt (注意: 是有正负的)

我们要求的面积为

这样计算并不需要分类讨论直线在圆心的上方还是下方。

标程

C++

正常写法:固定方向水平,二分与原点的距离

#include <bits/stdc++.h>

using namespace std;

constexpr double pi = 3.14159265358979323846;

struct Circle
{
    int x, y, r;
};

pair<double, double> split(double r, double d)
{
    double S = r * r * pi;
    if (d >= r)
    {
        return {0.0, S};
    }
    if (d <= -r)
    {
        return {S, 0.0};
    }
    double theta = acos(d / r);
    double A = r * r * theta - d * sqrt(r * r - d * d);
    return {A, S - A};
}

pair<double, double> check(const vector<Circle> &a, double y)
{
    double S1 = 0.0, S2 = 0.0;
    for (const Circle &c : a)
    {
        double d = y - c.y;
        auto [s1, s2] = split(c.r, d);
        S1 += s1;
        S2 += s2;
    }
    return {S1, S2};
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    cout.setf(ios::fixed);
    cout.precision(18);

    int n;
    cin >> n;
    vector<Circle> a(n);
    double S = 0.0;
    for (auto &[x, y, r] : a)
    {
        cin >> x >> y >> r;
        S += r * r * pi;
    }
    double l = 0, r = 1e4, mid;
    while (true)
    {
        mid = (l + r) / 2;
        auto [S1, S2] = check(a, mid);
        if (fabs(S1 - S2) <= 1e-9 * S)
        {
            break;
        }
        if (S1 > S2)
        {
            l = mid;
        }
        else
        {
            r = mid;
        }
    }
    double A = 0.0, B = 1.0, C = -mid;
    cout << A << '\n'
         << B << '\n'
         << C << '\n';
}

整活写法:随机一个方向,二分与原点的距离

#include <bits/stdc++.h>

using namespace std;

constexpr double pi = 3.14159265358979323846;

struct Circle
{
    double x, y, r;
};

pair<double, double> split(double r, double d)
{
    double S = r * r * pi;
    if (d >= r)
    {
        return {0.0, S};
    }
    if (d <= -r)
    {
        return {S, 0.0};
    }
    double theta = acos(d / r);
    double A = r * r * theta - d * sqrt(r * r - d * d);
    return {A, S - A};
}

pair<double, double> check(const vector<Circle> &a, double y)
{
    double S1 = 0.0, S2 = 0.0;
    for (const Circle &c : a)
    {
        double d = y - c.y;
        auto [s1, s2] = split(c.r, d);
        S1 += s1;
        S2 += s2;
    }
    return {S1, S2};
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    cout.setf(ios::fixed);
    cout.precision(18);

    int n;
    cin >> n;
    vector<Circle> a(n);
    double S = 0.0;
    for (auto &c : a)
    {
        int x, y, r;
        cin >> x >> y >> r;
        c.x = x;
        c.y = y;
        c.r = r;
        S += r * r * pi;
    }

    mt19937 rng(19260817);
    double theta = uniform_real_distribution<double>(0, 2 * pi)(rng);
    for (auto &c : a)
    {
        double x = c.x * cos(theta) - c.y * sin(theta);
        double y = c.x * sin(theta) + c.y * cos(theta);
        c.x = x;
        c.y = y;
    }

    double l = -1e5, r = 1e5, mid;
    while (true)
    {
        mid = (l + r) / 2;
        auto [S1, S2] = check(a, mid);
        if (fabs(S1 - S2) <= 1e-9 * S)
        {
            break;
        }
        if (S1 > S2)
        {
            l = mid;
        }
        else
        {
            r = mid;
        }
    }
    double A = sin(theta), B = cos(theta), C = -mid;
    cout << A << '\n'
         << B << '\n'
         << C << '\n';
}

Java

import java.util.*;
import java.io.*;

public class Main {
    static Kattio io = new Kattio();

    static int n;
    static int[] x, y, r;

    static double split(double r, double d) {
        if (d >= r) {
            return 0;
        }
        if (d <= -r) {
            return r * r * Math.PI;
        }
        double theta = Math.acos(d / r);
        return r * r * theta - d * Math.sqrt(r * r - d * d);
    }

    static double check(double b) { // y = b
        double res = 0;
        for (int i = 0; i < n; i++) {
            double d = b - y[i];
            res += split(r[i], d);
        }
        return res;
    }

    public static void main(String[] args) {
        n = io.nextInt();
        x = new int[n];
        y = new int[n];
        r = new int[n];
        double S = 0;
        for (int i = 0; i < n; i++) {
            x[i] = io.nextInt();
            y[i] = io.nextInt();
            r[i] = io.nextInt();
            S += r[i] * r[i] * Math.PI;
        }
        double l = 0, r = 1e4, mid;
        while (true) {
            mid = (l + r) / 2;
            double res = check(mid);
            double S1 = res, S2 = S - res;
            if (Math.abs(S1 - S2) <= 1e-9 * S) {
                break;
            }
            if (S1 > S2) {
                l = mid;
            } else {
                r = mid;
            }
        }
        double A = 0.0, B = 1.0, C = -mid; // y = mid
        io.printf("%.18f\n", A);
        io.printf("%.18f\n", B);
        io.printf("%.18f\n", C);

        io.close();
    }
}

class Kattio extends PrintWriter {
    private BufferedReader r;
    private StringTokenizer st;
    // 标准 IO
    public Kattio() { this(System.in, System.out); }
    public Kattio(InputStream i, OutputStream o) {
        super(o);
        r = new BufferedReader(new InputStreamReader(i));
    }
    // 在没有其他输入时返回 null
    public String next() {
        try {
            while (st == null || !st.hasMoreTokens())
                st = new StringTokenizer(r.readLine());
            return st.nextToken();
        } catch (Exception e) {}
        return null;
    }
    public int nextInt() { return Integer.parseInt(next()); }
    public double nextDouble() { return Double.parseDouble(next()); }
    public long nextLong() { return Long.parseLong(next()); }
}

Python

固定经过原点,二分倾斜角

import math


def split(r: float, d: float):
    S = r * r * math.pi
    if d >= r:
        return 0.0, S
    if d <= -r:
        return S, 0.0
    theta = math.acos(d / r)
    A = r * r * theta - d * math.sqrt(r * r - d * d)
    return A, S - A


def check(A: float, B: float, C: float):
    S1, S2 = 0.0, 0.0
    for x, y, r in circles:
        # d 表示圆心到直线的距离(有向)
        # 这里省略了除法,因此需要保证 A^2 + B^2 = 1
        d = A * x + B * y + C
        s1, s2 = split(r, d)
        S1 += s1
        S2 += s2
    return S1, S2


n = int(input())
circles = []
S = 0.0
for i in range(n):
    x, y, r = map(float, input().split())
    circles.append((x, y, r))
    S += r * r * math.pi

l, r = 0, math.pi / 2
while True:
    theta = (l + r) / 2
    # 与 x 轴的夹角为 theta 的直线
    A = math.sin(theta)
    B = -math.cos(theta)
    C = 0.0
    S1, S2 = check(A, B, C)
    if abs(S1 - S2) < 1e-9 * S:
        break
    if S1 > S2:
        l = theta
    else:
        r = theta

print(f"{A:.18f}")
print(f"{B:.18f}")
print(f"{C:.18f}")