結果

問題 No.2180 Comprehensive Line Segments
コンテスト
ユーザー butsurizuki
提出日時 2026-07-22 12:07:46
言語 C++23
(gcc 15.2.0 + boost 1.90.0)
コンパイル:
g++-15 -O2 -lm -std=c++23 -Wuninitialized -DONLINE_JUDGE -o a.out _filename_
実行:
./a.out
結果
AC  
実行時間 1,474 ms / 5,000 ms
+ 224µs
コード長 17,626 bytes
記録
記録タグの例:
初AC ショートコード 純ショートコード 純主流ショートコード 最速実行時間
コンパイル時間 4,247 ms
コンパイル使用メモリ 367,544 KB
実行使用メモリ 140,000 KB
最終ジャッジ日時 2026-07-22 12:07:57
合計ジャッジ時間 10,281 ms
ジャッジサーバーID
(参考情報)
judge3_0 / judge2_0
このコードへのチャレンジ
(要ログイン)
ファイルパターン 結果
sample AC * 4
other AC * 25
権限があれば一括ダウンロードができます

ソースコード

diff #
raw source code

#include <bits/stdc++.h>
using namespace std;

using i64 = long long;
using i128 = __int128_t;

struct Vec {
    i64 x, y;

    bool operator==(const Vec& other) const {
        return x == other.x && y == other.y;
    }
    bool operator<(const Vec& other) const {
        return x != other.x ? x < other.x : y < other.y;
    }
};

i64 cross(const Vec& a, const Vec& b) {
    return a.x * b.y - a.y * b.x;
}

i64 dot(const Vec& a, const Vec& b) {
    return a.x * b.x + a.y * b.y;
}

Vec operator-(const Vec& a, const Vec& b) {
    return {a.x - b.x, a.y - b.y};
}

Vec normalize(Vec a) {
    const i64 g = gcd(abs(a.x), abs(a.y));
    a.x /= g;
    a.y /= g;
    return a;
}

int half_plane(const Vec& a) {
    return (a.y > 0 || (a.y == 0 && a.x >= 0)) ? 0 : 1;
}

struct Cone {
    // full == false:
    //   l == r なら方向 l の一本の半直線。
    //   l != r なら l から r まで反時計回りの角度区間。
    uint16_t l = 0, r = 0;
    bool include_l = true, include_r = true;
    bool full = false;
};

class ConeSystem {
public:
    vector<Vec> dir;
    int m = 0;
    int max_cones = 0;
    int full_id = -1;
    vector<int> exact_id;

private:
    vector<Cone> cones;
    unordered_map<uint32_t, int> cone_id;
    vector<int> trans;

    uint32_t cone_key(const Cone& c) const {
        return static_cast<uint32_t>(c.l)
             | (static_cast<uint32_t>(c.r) << 8)
             | (static_cast<uint32_t>(c.include_l) << 16)
             | (static_cast<uint32_t>(c.include_r) << 17)
             | (static_cast<uint32_t>(c.full) << 18);
    }

    int intern(const Cone& c) {
        const uint32_t key = cone_key(c);
        if (const auto it = cone_id.find(key); it != cone_id.end()) {
            return it->second;
        }
        const int id = static_cast<int>(cones.size());
        assert(id < max_cones);
        cones.push_back(c);
        cone_id.emplace(key, id);
        return id;
    }

    static int compare_fraction(i64 n1, i64 d1, i64 n2, i64 d2) {
        const i128 z = static_cast<i128>(n1) * d2
                     - static_cast<i128>(n2) * d1;
        return (z > 0) - (z < 0);
    }

    // 条件 A-Bt >= 0(non_strict=true)または A-Bt > 0 と
    // t>0 を同時に満たす t が存在するか。
    static bool feasible_t(
        const array<array<i64, 3>, 2>& cond,
        int count
    ) {
        i64 lower_num = 0, lower_den = 1;
        bool lower_inclusive = false;  // t > 0

        bool has_upper = false;
        i64 upper_num = 0, upper_den = 1;
        bool upper_inclusive = false;

        for (int i = 0; i < count; ++i) {
            const i64 A = cond[i][0];
            const i64 B = cond[i][1];
            const bool non_strict = static_cast<bool>(cond[i][2]);

            if (B == 0) {
                if (non_strict ? (A < 0) : (A <= 0)) return false;
                continue;
            }

            if (B > 0) {
                // t <= A/B または t < A/B
                const i64 num = A, den = B;
                if (!has_upper) {
                    has_upper = true;
                    upper_num = num;
                    upper_den = den;
                    upper_inclusive = non_strict;
                } else {
                    const int cmp = compare_fraction(
                        num, den, upper_num, upper_den
                    );
                    if (cmp < 0) {
                        upper_num = num;
                        upper_den = den;
                        upper_inclusive = non_strict;
                    } else if (cmp == 0) {
                        upper_inclusive =
                            upper_inclusive && non_strict;
                    }
                }
            } else {
                // t >= A/B または t > A/B
                const i64 num = -A, den = -B;
                const int cmp = compare_fraction(
                    num, den, lower_num, lower_den
                );
                if (cmp > 0) {
                    lower_num = num;
                    lower_den = den;
                    lower_inclusive = non_strict;
                } else if (cmp == 0) {
                    lower_inclusive =
                        lower_inclusive && non_strict;
                }
            }
        }

        if (!has_upper) return true;

        const int cmp = compare_fraction(
            lower_num, lower_den, upper_num, upper_den
        );
        if (cmp < 0) return true;
        if (cmp > 0) return false;
        return lower_inclusive && upper_inclusive;
    }

    // v = s*a + t*w, s>0,t>0 が解を持つか。
    static bool intersects_exact_ray(
        const Vec& a,
        const Vec& v,
        const Vec& w
    ) {
        const i64 den = cross(a, w);
        if (den != 0) {
            const i64 s_num = cross(v, w);
            const i64 t_num = cross(a, v);
            return den > 0
                ? (s_num > 0 && t_num > 0)
                : (s_num < 0 && t_num < 0);
        }

        if (cross(a, v) != 0) return false;

        if (dot(w, a) < 0) return true;
        return dot(v, a) > 0;
    }

    // 半直線 {v-t*w | t>0} が cone の正の部分と交わるか。
    bool ray_intersects_cone(
        int cid,
        const Vec& v,
        const Vec& w
    ) const {
        const Cone c = cones[cid];
        if (c.full) return true;

        if (c.l == c.r) {
            return intersects_exact_ray(dir[c.l], v, w);
        }

        const Vec L = dir[c.l];
        const Vec R = dir[c.r];
        const int width = (c.r - c.l + m) % m;

        if (width == m / 2) {
            array<array<i64, 3>, 2> cond{};
            cond[0] = {cross(L, v), cross(L, w), 0};

            if (feasible_t(cond, 1)) return true;
            if (c.include_l &&
                intersects_exact_ray(L, v, w)) {
                return true;
            }
            if (c.include_r &&
                intersects_exact_ray(R, v, w)) {
                return true;
            }
            return false;
        }

        array<array<i64, 3>, 2> cond{};
        cond[0] = {
            cross(L, v),
            cross(L, w),
            c.include_l
        };
        cond[1] = {
            cross(v, R),
            cross(w, R),
            c.include_r
        };
        return feasible_t(cond, 2);
    }

    // 各 dir[k] と、その直後の開区間の所属可否から
    // cone を復元する。
    int canonicalize(const unsigned char* belongs) {
        const int atom_count = 2 * m;

        bool all = true;
        for (int i = 0; i < atom_count; ++i) {
            if (!belongs[i]) {
                all = false;
                break;
            }
        }
        if (all) return full_id;

        int zero = 0;
        while (belongs[zero]) ++zero;

        int runs = 0, first = -1, last = -1;
        bool in_run = false;

        for (int step = 1; step < atom_count; ++step) {
            const int q = (zero + step) % atom_count;

            if (belongs[q] && !in_run) {
                in_run = true;
                ++runs;
                if (runs == 1) first = q;
            }

            if (!belongs[q] && in_run) {
                if (runs == 1) {
                    last = (q - 1 + atom_count) % atom_count;
                }
                in_run = false;
            }
        }

        if (in_run && runs == 1) {
            last = (zero - 1 + atom_count) % atom_count;
        }

        assert(runs == 1);

        int l, r;
        bool include_l, include_r;

        if ((first & 1) == 0) {
            l = first / 2;
            include_l = true;
        } else {
            l = first / 2;
            include_l = false;
        }

        if ((last & 1) == 0) {
            r = last / 2;
            include_r = true;
        } else {
            r = (last / 2 + 1) % m;
            include_r = false;
        }

        if (l == r) {
            assert(first == last && (first & 1) == 0);
            return exact_id[l];
        }

        const int width = (r - l + m) % m;
        assert(width <= m / 2);

        return intern(Cone{
            static_cast<uint16_t>(l),
            static_cast<uint16_t>(r),
            include_l,
            include_r,
            false
        });
    }

public:
    explicit ConeSystem(const vector<Vec>& p) {
        set<Vec> all_directions;
        const int n = static_cast<int>(p.size());

        for (int i = 0; i < n; ++i) {
            for (int j = 0; j < n; ++j) {
                if (i != j) {
                    all_directions.insert(
                        normalize(p[j] - p[i])
                    );
                }
            }
        }

        if (all_directions.empty()) {
            all_directions.insert({1, 0});
            all_directions.insert({-1, 0});
        }

        dir.assign(
            all_directions.begin(),
            all_directions.end()
        );

        sort(dir.begin(), dir.end(),
             [](const Vec& a, const Vec& b) {
            const int ha = half_plane(a);
            const int hb = half_plane(b);
            if (ha != hb) return ha < hb;
            return cross(a, b) > 0;
        });

        m = static_cast<int>(dir.size());
        assert(m % 2 == 0);

        max_cones = 1 + m + 2 * m * m;

        cones.reserve(max_cones);
        cone_id.reserve(2 * max_cones);
        trans.assign(max_cones * m, -1);

        full_id = intern(Cone{0, 0, true, true, true});

        exact_id.resize(m);
        for (int k = 0; k < m; ++k) {
            exact_id[k] = intern(Cone{
                static_cast<uint16_t>(k),
                static_cast<uint16_t>(k),
                true,
                true,
                false
            });
        }
    }

    bool contains_direction(int cid, int k) const {
        const Cone& c = cones[cid];

        if (c.full) return true;
        if (c.l == c.r) return k == c.l;

        const int total = (c.r - c.l + m) % m;
        const int dist = (k - c.l + m) % m;

        if (dist == 0) return c.include_l;
        if (dist == total) return c.include_r;

        return 0 < dist && dist < total;
    }

    int move_to_single_point(
        int cid,
        int target_direction
    ) {
        int& memo = trans[cid * m + target_direction];
        if (memo != -1) return memo;

        if (contains_direction(cid, target_direction)) {
            memo = full_id;
            return memo;
        }

        const Vec v = dir[target_direction];
        unsigned char belongs[264]{};

        for (int k = 0; k < m; ++k) {
            belongs[2 * k] =
                static_cast<unsigned char>(
                    k == target_direction ||
                    ray_intersects_cone(cid, v, dir[k])
                );

            Vec sample{
                dir[k].x + dir[(k + 1) % m].x,
                dir[k].y + dir[(k + 1) % m].y
            };

            if (sample.x == 0 && sample.y == 0) {
                sample = {-dir[k].y, dir[k].x};
            }

            belongs[2 * k + 1] =
                static_cast<unsigned char>(
                    ray_intersects_cone(cid, v, sample)
                );
        }

        memo = canonicalize(belongs);
        return memo;
    }
};

uint32_t pack_state(int mask, int point, int cone) {
    return static_cast<uint32_t>(mask)
         | (static_cast<uint32_t>(point) << 12)
         | (static_cast<uint32_t>(cone) << 16);
}

int solve(const vector<Vec>& p) {
    const int n = static_cast<int>(p.size());
    const int goal = (1 << n) - 1;

    ConeSystem cs(p);

    map<Vec, int> direction_index;
    for (int k = 0; k < cs.m; ++k) {
        direction_index[cs.dir[k]] = k;
    }

    vector<vector<int>> direction(
        n, vector<int>(n, -1)
    );

    for (int i = 0; i < n; ++i) {
        for (int j = 0; j < n; ++j) {
            if (i != j) {
                direction[i][j] =
                    direction_index[
                        normalize(p[j] - p[i])
                    ];
            }
        }
    }

    vector<vector<int>> on_segment(
        n, vector<int>(n)
    );

    for (int i = 0; i < n; ++i) {
        for (int j = 0; j < n; ++j) {
            if (i == j) {
                on_segment[i][j] = 1 << i;
                continue;
            }

            int mask = 0;
            const Vec v = p[j] - p[i];

            for (int k = 0; k < n; ++k) {
                const Vec w = p[k] - p[i];

                if (cross(v, w) == 0
                    && min(p[i].x, p[j].x) <= p[k].x
                    && p[k].x <= max(p[i].x, p[j].x)
                    && min(p[i].y, p[j].y) <= p[k].y
                    && p[k].y <= max(p[i].y, p[j].y)) {
                    mask |= 1 << k;
                }
            }

            on_segment[i][j] = mask;
        }
    }

    vector<int> block((1 << n) * n, -1);
    int block_count = 0;

    for (int mask = 1; mask <= goal; ++mask) {
        for (int i = 0; i < n; ++i) {
            if ((mask >> i) & 1) {
                block[mask * n + i] = block_count++;
            }
        }
    }

    const uint64_t bit_count =
        static_cast<uint64_t>(block_count)
        * cs.max_cones;

    vector<uint64_t> visited(
        (bit_count + 63) / 64, 0
    );

    auto mark =
        [&](int mask, int last, int cone) -> bool {
        const uint64_t q =
            static_cast<uint64_t>(
                block[mask * n + last]
            ) * cs.max_cones + cone;

        const uint64_t bit = 1ULL << (q & 63);
        uint64_t& word = visited[q >> 6];

        if (word & bit) return false;

        word |= bit;
        return true;
    };

    vector<uint32_t> current, next;
    current.reserve(1 << 12);

    auto add_state =
        [&](int mask,
            int last,
            int cone,
            vector<uint32_t>& q) {
        if (mark(mask, last, cone)) {
            q.push_back(pack_state(mask, last, cone));
        }
    };

    // 最初の線分で入力点を一つだけ登録する。
    for (int i = 0; i < n; ++i) {
        add_state(
            1 << i,
            i,
            cs.full_id,
            current
        );
    }

    // 最初の線分で入力点を二つ以上登録する。
    for (int i = 0; i < n; ++i) {
        for (int j = 0; j < n; ++j) {
            if (i == j) continue;

            add_state(
                on_segment[i][j],
                j,
                cs.exact_id[direction[i][j]],
                current
            );
        }
    }

    for (const uint32_t state : current) {
        if (static_cast<int>(state & 4095U) == goal) {
            return 1;
        }
    }

    for (int segment_count = 2;
         segment_count <= n;
         ++segment_count) {
        next.clear();

        if (next.capacity() < current.size() * 2) {
            next.reserve(current.size() * 2);
        }

        for (const uint32_t state : current) {
            const int mask =
                static_cast<int>(state & 4095U);

            const int last =
                static_cast<int>((state >> 12) & 15U);

            const int cone =
                static_cast<int>(state >> 16);

            const int remaining = goal ^ mask;

            // この線分では新しい点を登録しない接続遷移。
            if (cone != cs.full_id) {
                add_state(
                    mask,
                    last,
                    cs.full_id,
                    next
                );
            }

            // 新しい線分上で最初に登録する点 j。
            for (int bits = remaining;
                 bits;
                 bits &= bits - 1) {
                const int j =
                    __builtin_ctz(
                        static_cast<unsigned>(bits)
                    );

                const int next_cone =
                    cs.move_to_single_point(
                        cone,
                        direction[last][j]
                    );

                const int one_point_mask =
                    mask | (1 << j);

                if (one_point_mask == goal) {
                    return segment_count;
                }

                add_state(
                    one_point_mask,
                    j,
                    next_cone,
                    next
                );

                // 同じ線分上で最後に登録する別の点 k。
                const int without_j =
                    remaining ^ (1 << j);

                for (int more = without_j;
                     more;
                     more &= more - 1) {
                    const int k =
                        __builtin_ctz(
                            static_cast<unsigned>(more)
                        );

                    const int d = direction[j][k];

                    if (!cs.contains_direction(
                            next_cone, d)) {
                        continue;
                    }

                    const int new_mask =
                        mask | on_segment[j][k];

                    if (new_mask == goal) {
                        return segment_count;
                    }

                    add_state(
                        new_mask,
                        k,
                        cs.exact_id[d],
                        next
                    );
                }
            }
        }

        current.swap(next);
    }

    return n;
}

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

    int n;
    cin >> n;

    vector<Vec> p(n);
    for (Vec& v : p) {
        cin >> v.x >> v.y;
    }

    cout << solve(p) << '\n';
    return 0;
}
0