m1une's library

This documentation is automatically generated by online-judge-tools/verification-helper

View on GitHub

:heavy_check_mark: Lattice-Point Count
(geometry/lattice_point_count.hpp)

Overview

count_lattice_points counts the integer grid points in a bounded region described by closed linear inequalities. Each constraint has the form

\[a x + b y \leq c,\]

where all three coefficients are signed integers. The calculation is exact: rational boundary intersections and points on the boundary do not involve floating-point tolerances.

The implementation constructs the piecewise-rational lower and upper envelopes of the constraints. It finds the feasible projection onto the x-axis, then sums each envelope piece with Euclidean floor-sum. It does not iterate over the coordinate range.

Interface

template <class T>
struct LinearInequality {
    T a;
    T b;
    T c;
};

template <class T>
__int128_t count_lattice_points(
    const std::vector<LinearInequality<T>>& inequalities
);
Member / Function Description Complexity
LinearInequality<T>{a, b, c} Represents the closed constraint $ax+by\leq c$. $O(1)$
count_lattice_points(inequalities) Returns the number of integer pairs satisfying every constraint. $O(N\log N+N\log C)$ time and $O(N)$ memory

Here $N$ is inequalities.size(), and $C$ is the magnitude of the coefficients and feasible coordinates. The logarithmic factor after envelope construction comes from floor-sum.

T must be a signed integer type of at most 64 bits. The real feasible region must be bounded, even when it is empty, a point, or a segment. In particular, a bounded nonempty region has both a lower and an upper y-envelope. The integer x-coordinates in its projection must fit long long, and the returned count must fit signed __int128. These conditions are checked by assertions where possible.

When <boost/multiprecision/cpp_int.hpp> is available, the internal exact integer backend uses boost::multiprecision::int256_t. Otherwise it falls back to the library’s local BigInt. Define M1UNE_GEOMETRY_LATTICE_POINT_COUNT_USE_BOOST to 0 before including this header to force the fallback backend.

All inequalities are closed, so boundary grid points are included. A constraint with a == 0 and b == 0 is either redundant when c >= 0 or makes the result zero when c < 0. The function does not mutate its input.

Example

#include "geometry/lattice_point_count.hpp"

#include <iostream>
#include <vector>

int main() {
    using m1une::geometry::LinearInequality;
    using m1une::geometry::count_lattice_points;

    // x >= 0, y >= 0, and x + y <= 2.
    std::vector<LinearInequality<long long>> constraints;
    constraints.push_back(LinearInequality<long long>{-1, 0, 0});
    constraints.push_back(LinearInequality<long long>{0, -1, 0});
    constraints.push_back(LinearInequality<long long>{1, 1, 2});

    long long answer = static_cast<long long>(
        count_lattice_points(constraints)
    );
    std::cout << answer << "\n";  // 6
}

Depends on

Required by

Verified with

Code

#ifndef M1UNE_GEOMETRY_LATTICE_POINT_COUNT_HPP
#define M1UNE_GEOMETRY_LATTICE_POINT_COUNT_HPP 1

#include <algorithm>
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <limits>
#include <type_traits>
#include <utility>
#include <vector>

#ifndef M1UNE_GEOMETRY_LATTICE_POINT_COUNT_USE_BOOST
#if defined(__has_include)
#if __has_include(<boost/multiprecision/cpp_int.hpp>)
#define M1UNE_GEOMETRY_LATTICE_POINT_COUNT_USE_BOOST 1
#else
#define M1UNE_GEOMETRY_LATTICE_POINT_COUNT_USE_BOOST 0
#endif
#else
#define M1UNE_GEOMETRY_LATTICE_POINT_COUNT_USE_BOOST 0
#endif
#endif

#if M1UNE_GEOMETRY_LATTICE_POINT_COUNT_USE_BOOST
#include <boost/multiprecision/cpp_int.hpp>
#else
#include "../utilities/bigint.hpp"
#endif

namespace m1une {
namespace geometry {

template <class T>
struct LinearInequality {
    T a;
    T b;
    T c;
};

namespace lattice_point_count_detail {

#if M1UNE_GEOMETRY_LATTICE_POINT_COUNT_USE_BOOST
using Integer = boost::multiprecision::int256_t;

inline Integer signed_int128_max() {
    return (Integer(1) << 127) - 1;
}

inline __int128_t to_int128(const Integer& value) {
    assert(0 <= value);
    return static_cast<__int128_t>(value);
}
#else
using Integer = ::m1une::utilities::BigInt;

inline Integer signed_int128_max() {
    Integer result = 1;
    for (int bit = 0; bit < 127; ++bit) result *= 2;
    result -= 1;
    return result;
}

inline __int128_t to_int128(const Integer& value) {
    assert(0 <= value);
    __uint128_t result = 0;
    for (
        int index = static_cast<int>(value.a.size()) - 1;
        index >= 0;
        --index
    ) {
        result = result * Integer::BASE + static_cast<unsigned>(value.a[index]);
    }
    return static_cast<__int128_t>(result);
}
#endif

struct Fraction {
    Integer numerator = 0;
    Integer denominator = 1;

    Fraction() = default;

    Fraction(Integer numerator_value, Integer denominator_value)
        : numerator(numerator_value), denominator(denominator_value) {
        assert(denominator != 0);
        if (denominator < 0) {
            numerator = -numerator;
            denominator = -denominator;
        }
    }
};

inline bool operator<(const Fraction& left, const Fraction& right) {
    return left.numerator * right.denominator <
           right.numerator * left.denominator;
}

inline bool operator==(const Fraction& left, const Fraction& right) {
    return left.numerator * right.denominator ==
           right.numerator * left.denominator;
}

inline bool operator<=(const Fraction& left, const Fraction& right) {
    return !(right < left);
}

struct Bound {
    // -1 is negative infinity, 0 is finite, and 1 is positive infinity.
    int infinity = 0;
    Fraction value;

    static Bound negative_infinity() {
        Bound result;
        result.infinity = -1;
        return result;
    }

    static Bound positive_infinity() {
        Bound result;
        result.infinity = 1;
        return result;
    }

    static Bound finite(const Fraction& fraction) {
        Bound result;
        result.value = fraction;
        return result;
    }
};

inline bool operator<(const Bound& left, const Bound& right) {
    if (left.infinity != right.infinity) {
        return left.infinity < right.infinity;
    }
    if (left.infinity != 0) return false;
    return left.value < right.value;
}

inline bool operator==(const Bound& left, const Bound& right) {
    if (left.infinity != right.infinity) return false;
    return left.infinity != 0 || left.value == right.value;
}

inline bool operator<=(const Bound& left, const Bound& right) {
    return !(right < left);
}

inline Bound maximum(const Bound& left, const Bound& right) {
    return left < right ? right : left;
}

inline Bound minimum(const Bound& left, const Bound& right) {
    return right < left ? right : left;
}

struct Line {
    // Represents (slope_numerator * x + intercept_numerator) / denominator.
    Integer slope_numerator;
    Integer intercept_numerator;
    Integer denominator;
};

inline bool slope_less(const Line& left, const Line& right) {
    return left.slope_numerator * right.denominator <
           right.slope_numerator * left.denominator;
}

inline bool same_slope(const Line& left, const Line& right) {
    return left.slope_numerator * right.denominator ==
           right.slope_numerator * left.denominator;
}

inline bool intercept_less(const Line& left, const Line& right) {
    return left.intercept_numerator * right.denominator <
           right.intercept_numerator * left.denominator;
}

inline Fraction intersection(const Line& left, const Line& right) {
    Integer numerator =
        left.intercept_numerator * right.denominator -
        right.intercept_numerator * left.denominator;
    Integer denominator =
        right.slope_numerator * left.denominator -
        left.slope_numerator * right.denominator;
    assert(denominator > 0);
    return Fraction(numerator, denominator);
}

struct Envelope {
    std::vector<Line> lines;
    // starts[i] is the finite x-coordinate where lines[i] becomes maximal.
    // starts[0] is unused because lines[0] starts at negative infinity.
    std::vector<Fraction> starts;
};

inline Envelope maximum_envelope(std::vector<Line> lines) {
    std::sort(
        lines.begin(),
        lines.end(),
        [](const Line& left, const Line& right) {
            if (!same_slope(left, right)) return slope_less(left, right);
            return intercept_less(right, left);
        }
    );

    std::vector<Line> unique;
    for (const Line& line : lines) {
        if (unique.empty() || !same_slope(unique.back(), line)) {
            unique.push_back(line);
        }
    }

    Envelope result;
    for (const Line& line : unique) {
        Fraction start;
        while (!result.lines.empty()) {
            start = intersection(result.lines.back(), line);
            if (
                result.lines.size() == 1 ||
                result.starts.back() < start
            ) {
                break;
            }
            result.lines.pop_back();
            result.starts.pop_back();
        }
        result.lines.push_back(line);
        if (result.lines.size() == 1) {
            result.starts.emplace_back();
        } else {
            result.starts.push_back(start);
        }
    }
    return result;
}

inline Bound segment_start(const Envelope& envelope, std::size_t index) {
    if (index == 0) return Bound::negative_infinity();
    return Bound::finite(envelope.starts[index]);
}

inline Bound segment_end(const Envelope& envelope, std::size_t index) {
    if (index + 1 == envelope.lines.size()) {
        return Bound::positive_infinity();
    }
    return Bound::finite(envelope.starts[index + 1]);
}

inline Integer floor_div(Integer numerator, const Integer& denominator) {
    assert(denominator > 0);
    Integer quotient = numerator / denominator;
    Integer remainder = numerator % denominator;
    if (remainder < 0) quotient -= 1;
    return quotient;
}

inline Integer ceil_div(Integer numerator, const Integer& denominator) {
    return -floor_div(-numerator, denominator);
}

inline Integer floor_sum(
    Integer count,
    Integer modulus,
    Integer multiplier,
    Integer constant
) {
    assert(count >= 0);
    assert(modulus > 0);

    Integer answer = 0;
    Integer quotient = floor_div(multiplier, modulus);
    answer += quotient * count * (count - 1) / 2;
    multiplier -= quotient * modulus;

    quotient = floor_div(constant, modulus);
    answer += quotient * count;
    constant -= quotient * modulus;

    while (true) {
        if (multiplier >= modulus) {
            answer += count * (count - 1) / 2 * (multiplier / modulus);
            multiplier %= modulus;
        }
        if (constant >= modulus) {
            answer += count * (constant / modulus);
            constant %= modulus;
        }

        Integer maximum = multiplier * count + constant;
        if (maximum < modulus) break;
        count = maximum / modulus;
        constant = maximum % modulus;
        std::swap(modulus, multiplier);
    }
    return answer;
}

inline std::pair<Bound, Bound> feasible_projection(
    const Envelope& lower,
    const Envelope& negative_upper,
    Bound domain_lower,
    Bound domain_upper,
    bool& feasible
) {
    Bound result_lower = Bound::positive_infinity();
    Bound result_upper = Bound::negative_infinity();
    std::size_t lower_index = 0;
    std::size_t upper_index = 0;

    while (
        lower_index < lower.lines.size() &&
        upper_index < negative_upper.lines.size()
    ) {
        Bound lower_end = segment_end(lower, lower_index);
        Bound upper_end = segment_end(negative_upper, upper_index);
        Bound left = maximum(
            maximum(
                segment_start(lower, lower_index),
                segment_start(negative_upper, upper_index)
            ),
            domain_lower
        );
        Bound right = minimum(minimum(lower_end, upper_end), domain_upper);

        const Line& lower_line = lower.lines[lower_index];
        const Line& upper_line = negative_upper.lines[upper_index];
        Integer slope =
            lower_line.slope_numerator * upper_line.denominator +
            upper_line.slope_numerator * lower_line.denominator;
        Integer intercept =
            lower_line.intercept_numerator * upper_line.denominator +
            upper_line.intercept_numerator * lower_line.denominator;

        if (slope > 0) {
            right = minimum(
                right,
                Bound::finite(Fraction(-intercept, slope))
            );
        } else if (slope < 0) {
            left = maximum(
                left,
                Bound::finite(Fraction(-intercept, slope))
            );
        } else if (intercept > 0) {
            left = Bound::positive_infinity();
            right = Bound::negative_infinity();
        }

        if (left <= right) {
            if (!feasible) {
                result_lower = left;
                result_upper = right;
                feasible = true;
            } else {
                result_lower = minimum(result_lower, left);
                result_upper = maximum(result_upper, right);
            }
        }

        if (lower_end == upper_end) {
            ++lower_index;
            ++upper_index;
        } else if (lower_end < upper_end) {
            ++lower_index;
        } else {
            ++upper_index;
        }
    }
    return {result_lower, result_upper};
}

inline Integer sum_floor_of_negative(
    const Envelope& envelope,
    Integer first_x,
    Integer last_x
) {
    Integer answer = 0;
    for (std::size_t index = 0; index < envelope.lines.size(); ++index) {
        Integer left = first_x;
        Integer right = last_x;
        if (index > 0) {
            left = std::max(
                left,
                ceil_div(
                    envelope.starts[index].numerator,
                    envelope.starts[index].denominator
                )
            );
        }
        if (index + 1 < envelope.lines.size()) {
            right = std::min(
                right,
                ceil_div(
                    envelope.starts[index + 1].numerator,
                    envelope.starts[index + 1].denominator
                ) - 1
            );
        }
        if (right < left) continue;

        const Line& line = envelope.lines[index];
        Integer count = right - left + 1;
        Integer multiplier = -line.slope_numerator;
        Integer constant = -line.intercept_numerator + multiplier * left;
        answer += floor_sum(
            count,
            line.denominator,
            multiplier,
            constant
        );
    }
    return answer;
}

}  // namespace lattice_point_count_detail

// Counts integer pairs (x, y) satisfying every closed inequality
// a * x + b * y <= c. The real feasible region must be bounded.
template <class T>
__int128_t count_lattice_points(
    const std::vector<LinearInequality<T>>& inequalities
) {
    static_assert(
        std::is_integral_v<T> && std::is_signed_v<T> && sizeof(T) <= 8,
        "count_lattice_points requires a signed integer type of at most 64 bits"
    );
    namespace detail = lattice_point_count_detail;
    using detail::Bound;
    using detail::Envelope;
    using detail::Fraction;
    using detail::Integer;
    using detail::Line;

    std::vector<Line> lower_lines;
    std::vector<Line> negative_upper_lines;
    Bound domain_lower = Bound::negative_infinity();
    Bound domain_upper = Bound::positive_infinity();

    for (const LinearInequality<T>& inequality : inequalities) {
        Integer a = inequality.a;
        Integer b = inequality.b;
        Integer c = inequality.c;
        if (b < 0) {
            lower_lines.push_back(Line{a, -c, -b});
        } else if (b > 0) {
            negative_upper_lines.push_back(Line{a, -c, b});
        } else if (a > 0) {
            domain_upper = detail::minimum(
                domain_upper,
                Bound::finite(Fraction(c, a))
            );
        } else if (a < 0) {
            domain_lower = detail::maximum(
                domain_lower,
                Bound::finite(Fraction(c, a))
            );
        } else if (c < 0) {
            return 0;
        }
    }

    if (domain_upper < domain_lower) return 0;
    // Without both y-envelopes, every feasible x-column is unbounded.
    assert(!lower_lines.empty() && !negative_upper_lines.empty());

    Envelope lower = detail::maximum_envelope(std::move(lower_lines));
    Envelope negative_upper =
        detail::maximum_envelope(std::move(negative_upper_lines));

    bool feasible = false;
    auto projection = detail::feasible_projection(
        lower,
        negative_upper,
        domain_lower,
        domain_upper,
        feasible
    );
    if (!feasible) return 0;
    assert(projection.first.infinity == 0);
    assert(projection.second.infinity == 0);

    Integer first_x = detail::ceil_div(
        projection.first.value.numerator,
        projection.first.value.denominator
    );
    Integer last_x = detail::floor_div(
        projection.second.value.numerator,
        projection.second.value.denominator
    );
    if (last_x < first_x) return 0;
    assert(first_x >= std::numeric_limits<long long>::min());
    assert(first_x <= std::numeric_limits<long long>::max());
    assert(last_x >= std::numeric_limits<long long>::min());
    assert(last_x <= std::numeric_limits<long long>::max());

    Integer answer = last_x - first_x + 1;
    answer += detail::sum_floor_of_negative(
        negative_upper,
        first_x,
        last_x
    );
    answer += detail::sum_floor_of_negative(lower, first_x, last_x);

    assert(answer >= 0);
    assert(answer <= detail::signed_int128_max());
    return detail::to_int128(answer);
}

}  // namespace geometry
}  // namespace m1une

#endif  // M1UNE_GEOMETRY_LATTICE_POINT_COUNT_HPP
Traceback (most recent call last):
  File "/home/runner/.local/lib/python3.12/site-packages/onlinejudge_verify/documentation/build.py", line 71, in _render_source_code_stat
    bundled_code = language.bundle(stat.path, basedir=basedir, options={'include_paths': [basedir]}).decode()
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/runner/.local/lib/python3.12/site-packages/onlinejudge_verify/languages/cplusplus.py", line 187, in bundle
    bundler.update(path)
  File "/home/runner/.local/lib/python3.12/site-packages/onlinejudge_verify/languages/cplusplus_bundle.py", line 400, in update
    raise BundleErrorAt(path, i + 1, "unable to process #include in #if / #ifdef / #ifndef other than include guards")
onlinejudge_verify.languages.cplusplus_bundle.BundleErrorAt: geometry/lattice_point_count.hpp: line 28: unable to process #include in #if / #ifdef / #ifndef other than include guards
Back to top page