线性分类器

代入直线方程检查两类点内部同号且两类代表符号相反。

OJ: shumeng

题目 ID: CSP202006A

难度:入门

标签:模拟几何分类

日期: 2026-07-31 16:21

形式化题目

nn 个带类别 A\text{A}B\text{B} 的平面点 (xi,yi)(x_i,y_i)mm 条直线

θ0+θ1x+θ2y=0, \theta_0+\theta_1x+\theta_2y=0,

对每条直线判断它是否把两类点完全分到两侧:一侧只有 A 类点,另一侧只有 B 类点。数据保证没有任何点恰好落在直线上。

思路

把每个点 (x,y)(x,y) 代入直线方程,得到的值的正负号表示这个点位于直线的哪一侧。因为不会出现零,只需要比较符号。

判断步骤

  1. 记录第一个 A 点与第一个 B 点的符号,作为两类各自的代表符号;
  2. 扫描全部点:A 点都必须与 A 的代表符号相同,B 点都必须与 B 的代表符号相同;
  3. 两个代表符号还必须相反,说明两类点分居直线两侧。

三个条件全部满足时输出 Yes,否则输出 No

先看一个朴素的两遍扫描实现,第一遍确定每类代表符号,第二遍验证所有点:

cpp
/**
 * Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
 * rbook: -> https://rbook.roj.ac.cn  https://rbook2.roj.ac.cn
 * rainboy的学习导航网站: https://idx.roj.ac.cn
 * create_at: 2026-07-31 16:21
 * update_at: 2026-08-17 22:39
 */
// brute.cpp:小数据暴力解,两遍扫描先确定每类代表符号,再验证所有点。
#include <bits/stdc++.h>
using namespace std;

const int MAXN = 1005;

int n, query_count;
long long x[MAXN], y[MAXN];   // 点的横坐标、纵坐标
char type[MAXN];              // 点的类别:A 或 B

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

    cin >> n >> query_count;
    for (int i = 1; i <= n; i++) cin >> x[i] >> y[i] >> type[i];

    while (query_count--) {
        long long theta0, theta1, theta2;
        cin >> theta0 >> theta1 >> theta2;

        // 第一遍:确定 A、B 两类各自的代表符号
        int sign_a = 0, sign_b = 0;
        for (int i = 1; i <= n; i++) {
            long long value = theta0 + theta1 * x[i] + theta2 * y[i];
            int sign = value > 0 ? 1 : -1;
            if (type[i] == 'A' && sign_a == 0) sign_a = sign;
            if (type[i] == 'B' && sign_b == 0) sign_b = sign;
        }

        // 第二遍:验证所有点与各自类别代表符号一致,且两类符号相反
        bool correct = sign_a != sign_b;
        for (int i = 1; i <= n; i++) {
            long long value = theta0 + theta1 * x[i] + theta2 * y[i];
            int sign = value > 0 ? 1 : -1;
            if (type[i] == 'A' && sign != sign_a) correct = false;
            if (type[i] == 'B' && sign != sign_b) correct = false;
        }
        cout << (correct ? "Yes" : "No") << '\n';
    }

    return 0;
}

代码

cpp
/**
 * Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
 * rbook: -> https://rbook.roj.ac.cn  https://rbook2.roj.ac.cn
 * rainboy的学习导航网站: https://idx.roj.ac.cn
 * create_at: 2026-07-31 16:21
 * update_at: 2026-08-17 22:39
 */
#include <bits/stdc++.h>
using namespace std;

const int MAXN = 1005;

int n, query_count;
long long x[MAXN], y[MAXN];   // 点的横坐标、纵坐标
char type[MAXN];              // 点的类别:A 或 B

// 返回 value 的符号:正为 1,负为 -1(题目保证点不落在直线上)
int get_sign(long long value) {
    return value > 0 ? 1 : -1;
}

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

    cin >> n >> query_count;
    for (int i = 1; i <= n; i++) cin >> x[i] >> y[i] >> type[i];

    while (query_count--) {
        long long theta0, theta1, theta2;
        cin >> theta0 >> theta1 >> theta2;

        int sign_a = 0, sign_b = 0;   // A 类与 B 类各自的代表符号
        bool correct = true;
        for (int i = 1; i <= n; i++) {
            // 代入直线方程,结果的正负表示点在直线的哪一侧
            long long value = theta0 + theta1 * x[i] + theta2 * y[i];
            int sign = get_sign(value);
            if (type[i] == 'A') {
                if (sign_a == 0) sign_a = sign;
                else if (sign_a != sign) correct = false;
            } else {
                if (sign_b == 0) sign_b = sign;
                else if (sign_b != sign) correct = false;
            }
        }
        // 两类点必须在直线两侧,即代表符号相反
        if (sign_a == sign_b) correct = false;
        cout << (correct ? "Yes" : "No") << '\n';
    }

    return 0;
}

复杂度

每条查询扫描全部 nn 个点,时间复杂度为 O(nm)O(nm),空间复杂度为 O(n)O(n)

总结

线性分类器的“分隔”条件不需要做复杂的几何运算。把所有点代入同一个一次式,检查同类同号、异类异号即可。