Помилкові позитиви на цілісну решітку


12

Таблиця лідерів

 User            Language      Score
 =========================================
 Ell             C++11         293,619,555
 feersum         C++11         100,993,667
 Ell             C++11          78,824,732
 Geobits         Java           27,817,255
 Ell             Python         27,797,402
 Peter Taylor    Java                2,468
 <reference>     Julia                 530

Фон

Працюючи над 2-D сіткою цілих координат, іноді хочеться знати, чи мають два вектори (з цілими компонентами) однакову величину. Звичайно, в евклідовій геометрії величину вектора (x,y)задають значення

√(x² + y²)

Таким чином, наївна реалізація може обчислити це значення для обох векторів та порівняти результати. Це не тільки не потребує обчислення кореневих квадратних коренів, але також спричиняє проблеми з неточностями плаваючої точки, що може дати помилкові позитиви: вектори, величини яких різняться, але де значущі цифри у поданні з плаваючою точкою однакові.

Для цілей цього виклику ми визначаємо помилковий позитив як пару пар координат (a,b)і (c,d)для якого:

  • Їх величина в квадраті різна, якщо їх представлено у вигляді 64-бітних непідписаних цілих чисел.
  • Їх величина ідентична, якщо представлена ​​як 64-розрядне двійкове число з плаваючою точкою та обчислюється через 64-розрядний квадратний корінь (згідно IEEE 754 ).

Наприклад, використовуючи 16-бітні подання (замість 64), найменша 1 пара векторів, яка дає помилковий позитив,

(25,20) and (32,0)

Їх квадратичні величини є 1025і 1024. Взяття врожаю квадратного кореня

32.01562118716424 and 32.0

Але в 16-бітових поплавках вони обидва врізаються 32.0.

Аналогічно, найменша 2 пара дає помилковий позитив для 32-бітових уявлень

(1659,1220) and (1951,659)

1 "Найменший", що вимірюється їх 16-бітовою величиною з плаваючою точкою.
2 "Найменший", що вимірюється їх 32-бітної величиною з плаваючою точкою.

Нарешті, ось декілька дійсних 64-розрядних випадків:

 (51594363,51594339) and (54792160,48184783)
 (54356775,54353746) and (54620742,54088476)
 (54197313,46971217) and (51758889,49645356)
 (67102042,  956863) and (67108864,       6) *

* Останній випадок - один із кількох з найменшою можливою величиною для 64-розрядних помилкових позитивів.

Змагання

Менш ніж 10 000 байтів коду, використовуючи один потік, ви можете знайти стільки помилкових позитивів для 64-розрядних (двійкових) чисел з плаваючою точкою в діапазоні координат 0 ≤ y ≤ x(тобто лише в межах першого октанта площини Евкліда) таким, що протягом 10 хвилин . Якщо два подання стосуються однакової кількості пар, то вимикач краватки - це фактичний час, необхідний для пошуку останньої з цих пар.x² + y² ≤ 253

Ваша програма не повинна використовувати більше 4 ГБ пам'яті в будь-який час (з практичних причин).

Потрібно мати можливість запускати свою програму в двох режимах: в одному, який видає кожну пару, як вона знаходить, і такому, який видає лише кількість знайдених пар в кінці. Перший буде використаний для перевірки дійсності ваших пар (переглянувши деякий зразок результатів), а другий буде використаний для фактичного визначення часу подання вами. Зауважте, що друк повинен бути єдиною різницею. Зокрема, програма підрахунку може не жорстко кодувати кількість пар, які вона могла б знайти. Він все одно повинен виконувати той самий цикл, який був би використаний для друку всіх номерів, і лише опускати друк!

Я перевіряю всі матеріали на своєму ноутбуці Windows 8, тому, будь ласка, запитайте в коментарях, чи хочете ви використовувати якусь не надто загальну мову.

Зауважте, що пари не повинні рахуватися двічі під час перемикання першої та другої координатних пар.

Зауважте також, що я запускаю ваш процес через контролер Ruby, який знищить ваш процес, якщо він не закінчиться через 10 хвилин. Не забудьте вивести кількість знайдених на той час пар. Ви можете самостійно відслідковувати час і надрукувати результат перед тим, як минуть 10 хвилин, або ви просто виведете кількість пар, знайдених епізодично, і я візьму останнє таке число як ваш рахунок.


Як побічний коментар, можна одночасно визначити, чи є ціле число досконалим квадратом, а також ефективно обчислити його точний квадратний корінь. Наступний алгоритм у 5 разів швидший, ніж апаратний квадратний корінь у моїй системі (порівнюючи 64-розрядні цілі без підпису з 80-бітним довгим подвійним): math.stackexchange.com/questions/41337/…
Todd Lehman,

Відповіді:


5

C ++, 275 000 000+

Ми будемо називати пари, величина яких точно представлена, наприклад (x, 0) , як чесні пари, а всі інші пари як нечесні пари з величиною m , де m - неправильно повідомлена величина пари. Перша програма в попередньому дописі використовувала набір тісно пов'язаних пар чесних і нечесних пар:
(x, 0) та (x, 1) відповідно для досить великих x. Друга програма використовувала той самий набір нечесних пар, але розширила набір чесних пар, шукаючи всі чесні пари інтегральної величини. Програма не припиняється протягом десяти хвилин, але вона знаходить переважну більшість своїх результатів дуже рано, а це означає, що більша частина часу роботи втрачається. Замість того, щоб шукати все рідше чесних пар, ця програма використовує вільний час, щоб зробити наступну логічну справу: розширити набір нечесних пар.

З попереднього допису ми знаємо, що для всіх достатньо великих цілих чисел r , sqrt (r 2 + 1) = r , де sqrt є кореневою функцією квадрата з плаваючою комою. Наш план атаки полягає в тому, щоб знайти пари P = (x, y) такі, що x 2 + y 2 = r 2 + 1 для деякого досить великого цілого числа r . Це досить просто зробити, але наївно шукати окремих таких пар занадто повільно, щоб бути цікавим. Ми хочемо знайти ці пари оптом, як і для чесних пар у попередній програмі.

Нехай { v , w } - ортонормальна пара векторів. Для всіх реальних скалярів r , || r v + w || 2 = r 2 + 1 . У 2 це прямий результат теореми Піфагора:

Зображення 1

Ми шукаємо вектори v і w такі, що існує ціле число r, для якого x і y також цілі числа. Зауважимо, що набір нечесних пар, який ми використовували у попередніх двох програмах, був просто окремим випадком цього, де { v , w } є стандартною основою 2 ; цього разу ми хочемо знайти більш загальне рішення. Ось де піфагорійські трійки (цілі трійки (a, b, c), що задовольняють a 2 + b 2 = c 2, яку ми використовували в попередній програмі) повертаються.

Нехай (a, b, c) є піфагорійська трійка. Вектори v = (b / c, a / c) і w = (-a / c, b / c) (а також
w = (a / c, -b / c) ) є ортонормальними, як це легко перевірити . Як виявляється, для будь-якого вибору трифата Піфагора існує ціле число r таке, що x і y - цілі числа. Щоб довести це і ефективно знайти r і P , нам потрібно невелика теорія чисел / груп; Я збираюся шкодувати деталі. Так чи інакше, припустимо, ми маємо інтеграл r , x і y . Нам ще не вистачає кількох речей: нам потрібен rщоб бути досить великим, і ми хочемо, щоб швидкий метод отримав ще багато подібних пар з цього. На щастя, існує простий спосіб досягти цього.

Зауважимо, що проекція P на v є r v , отже r = P · v = (x, y) · (b / c, a / c) = xb / c + ya / c , все це говорить про те, що xb + ya = rc . В результаті, для всіх цілих п , (х + млрд) 2 + (у + а) 2 = (х 2 + у 2 ) + 2 (XB + уа) п + (а 2 + Ь 2 ) п 2 = ( r 2 + 1) + 2 (rc) n + (c 2 ) n 2 = (r + cn) 2 + 1. Іншими словами, квадратна величина пар форми
(x + bn, y + an) дорівнює (r + cn) 2 + 1 , що саме є тими парами, яких ми шукаємо! Для досить великих n це нечесні пари величини r + cn .

Завжди приємно дивитись на конкретний приклад. Якщо взяти піфагорову триплету (3, 4, 5) , то при r = 2 маємо P = (1, 2) (ви можете перевірити, що (1, 2) · (4/5, 3/5) = 2 і, зрозуміло, 1 2 + 2 2 = 2 2 + 1. ) Додавання 5 до r і (4, 3) до P приводить нас до r '= 2 + 5 = 7 і P' = (1 + 4, 2 + 3) = (5, 5) . Ось і ось, 5 2 + 5 2 = 7 2 + 1. Наступними координатами є r '' = 12 і P '' = (9, 8) , і знову, 9 2 + 8 2 = 12 2 + 1 , і так далі, і так далі ...

Зображення 2

Після того, як r досить великий, ми починаємо отримувати нечесні пари з кроком в 5 . Це приблизно 27 797 402/5 нечесних пар.

Тож зараз у нас є безліч нечесних пар цілісної величини. Ми можемо легко з'єднати їх з чесними парами першої програми, щоб сформувати помилкові позитиви, і з належною обережністю ми також можемо використовувати чесні пари другої програми. Це в основному те, що робить ця програма. Як і в попередній програмі, вона теж знаходить більшість своїх результатів дуже рано - вона потрапляє до 200 000 000 помилкових позитивних результатів протягом декількох секунд ---, а потім значно сповільнюється.

Компілювати з g++ flspos.cpp -oflspos -std=c++11 -msse2 -mfpmath=sse -O3. Щоб перевірити результати, додайте -DVERIFY(це буде помітно повільніше.)

Бігайте з flspos. Будь-який аргумент командного рядка для багатослівного режиму.

#include <cstdio>
#define _USE_MATH_DEFINES
#undef __STRICT_ANSI__
#include <cmath>
#include <cfloat>
#include <vector>
#include <iterator>
#include <algorithm>

using namespace std;

/* Make sure we actually work with 64-bit precision */
#if defined(VERIFY) && FLT_EVAL_METHOD != 0 && FLT_EVAL_METHOD != 1
#   error "invalid FLT_EVAL_METHOD (did you forget `-msse2 -mfpmath=sse'?)"
#endif

template <typename T> struct widen;
template <> struct widen<int> { typedef long long type; };

template <typename T>
inline typename widen<T>::type mul(T x, T y) {
    return typename widen<T>::type(x) * typename widen<T>::type(y);
}
template <typename T> inline T div_ceil(T a, T b) { return (a + b - 1) / b; }
template <typename T> inline typename widen<T>::type sq(T x) { return mul(x, x); }
template <typename T>
T gcd(T a, T b) { while (b) { T t = a; a = b; b = t % b; } return a; }
template <typename T>
inline typename widen<T>::type lcm(T a, T b) { return mul(a, b) / gcd(a, b); }
template <typename T>
T div_mod_n(T a, T b, T n) {
    if (b == 0) return a == 0 ? 0 : -1;
    const T n_over_b = n / b, n_mod_b = n % b;
    for (T m = 0; m < n; m += n_over_b + 1) {
        if (a % b == 0) return m + a / b;
        a -= b - n_mod_b;
        if (a < 0) a += n;
    }
    return -1;
}

template <typename T> struct pythagorean_triplet { T a, b, c; };
template <typename T>
struct pythagorean_triplet_generator {
    typedef pythagorean_triplet<T> result_type;
private:
    typedef typename widen<T>::type WT;
    result_type p_triplet;
    WT p_c2b2;
public:
    pythagorean_triplet_generator(const result_type& triplet = {3, 4, 5}) :
        p_triplet(triplet), p_c2b2(sq(triplet.c) - sq(triplet.b))
    {}
    const result_type& operator*() const { return p_triplet; }
    const result_type* operator->() const { return &p_triplet; }
    pythagorean_triplet_generator& operator++() {
        do {
            if (++p_triplet.b == p_triplet.c) {
                ++p_triplet.c;
                p_triplet.b = ceil(p_triplet.c * M_SQRT1_2);
                p_c2b2 = sq(p_triplet.c) - sq(p_triplet.b);
            } else
                p_c2b2 -= 2 * p_triplet.b - 1;
            p_triplet.a = sqrt(p_c2b2);
        } while (sq(p_triplet.a) != p_c2b2 || gcd(p_triplet.b, p_triplet.a) != 1);
        return *this;
    }
    result_type operator()() { result_type t = **this; ++*this; return t; }
};

int main(int argc, const char* argv[]) {
    const bool verbose = argc > 1;

    const int min = 1 << 26;
    const int max = sqrt(1ll << 53);

    const size_t small_triplet_count = 1000;
    vector<pythagorean_triplet<int>> small_triplets;
    small_triplets.reserve(small_triplet_count);
    generate_n(
        back_inserter(small_triplets),
        small_triplet_count,
        pythagorean_triplet_generator<int>()
    );

    int found = 0;
    auto add = [&] (int x1, int y1, int x2, int y2) {
#ifdef VERIFY
        auto n1 = sq(x1) + sq(y1), n2 = sq(x2) + sq(y2);
        if (x1 < y1 || x2 < y2 || x1 > max || x2 > max ||
            n1 == n2 || sqrt(n1) != sqrt(n2)
        ) {
            fprintf(stderr, "Wrong false-positive: (%d, %d) (%d, %d)\n",
                    x1, y1, x2, y2);
            return;
        }
#endif
        if (verbose) printf("(%d, %d) (%d, %d)\n", x1, y1, x2, y2);
        ++found;
    };

    int output_counter = 0;
    for (int x = min; x <= max; ++x) add(x, 0,    x, 1);
    for (pythagorean_triplet_generator<int> i; i->c <= max; ++i) {
        const auto& t1 = *i;

        for (int n = div_ceil(min, t1.c); n <= max / t1.c; ++n)
            add(n * t1.b, n * t1.a,    n * t1.c, 1);

        auto find_false_positives = [&] (int r, int x, int y) {
            {
                int n = div_ceil(min - r, t1.c);
                int min_r = r + n * t1.c;
                int max_n = n + (max - min_r) / t1.c;
                for (; n <= max_n; ++n)
                    add(r + n * t1.c, 0,    x + n * t1.b, y + n * t1.a);
            }
            for (const auto t2 : small_triplets) {
                int m = div_mod_n((t2.c - r % t2.c) % t2.c, t1.c % t2.c, t2.c);
                if (m < 0) continue;
                int sr = r + m * t1.c;
                int c = lcm(t1.c, t2.c);
                int min_n = div_ceil(min - sr, c);
                int min_r = sr + min_n * c;
                if (min_r > max) continue;
                int x1 = x + m * t1.b, y1 = y + m * t1.a;
                int x2 = t2.b * (sr / t2.c), y2 = t2.a * (sr / t2.c);
                int a1 = t1.a * (c / t1.c), b1 = t1.b * (c / t1.c);
                int a2 = t2.a * (c / t2.c), b2 = t2.b * (c / t2.c);
                int max_n = min_n + (max - min_r) / c;
                int max_r = sr + max_n * c;
                for (int n = min_n; n <= max_n; ++n) {
                    add(
                        x2 + n * b2, y2 + n * a2,
                        x1 + n * b1, y1 + n * a1
                    );
                }
            }
        };
        {
            int m = div_mod_n((t1.a - t1.c % t1.a) % t1.a, t1.b % t1.a, t1.a);
            find_false_positives(
                /* r = */ (mul(m, t1.c) + t1.b) / t1.a,
                /* x = */ (mul(m, t1.b) + t1.c) / t1.a,
                /* y = */ m
            );
        } {
            int m = div_mod_n((t1.b - t1.c % t1.b) % t1.b, t1.a, t1.b);
            find_false_positives(
                /* r = */ (mul(m, t1.c) + t1.a) / t1.b,
                /* x = */ m,
                /* y = */ (mul(m, t1.a) + t1.c) / t1.b
            );
        }

        if (output_counter++ % 50 == 0)
            printf("%d\n", found), fflush(stdout);
    }
    printf("%d\n", found);
}

Приємно! :) Я отримав 293 619 555 на своїй машині та оновив таблицю лідерів.
Мартін Ендер

8

Пітон, 27,797,402

Просто, щоб встановити планку трохи вище ...

from sys import argv
verbose = len(argv) > 1
found = 0
for x in xrange(67108864, 94906266):
    found += 1
    if verbose:
        print "(%d, 0) (%d, 1)" % (x, x)
print found

Неважко перевірити, що для всіх 67,108,864 <= x <= 94,906,265 = floor (sqrt (2 53 )) пари (x, 0) і (x, 1) є помилковими позитивними.

Чому це працює : 67,108,864 = 2 26 . Отже, всі числа x у наведеному діапазоні мають вигляд 2 26 + x ' для деяких 0 <= x' <2 26 . Для всіх позитивних е , (х + е) 2 = х 2 + 2xe + е 2 = х 2 + 2 27 е + + е 2x'e 2 . Якщо ми хочемо мати
(x + e) 2 = x 2 + 1, нам потрібно принаймні 2 27 e <= 1 , тобто e <= 2 -27 Однак, оскільки мантіса подвійних точних чисел з плаваючою точкою є 52-бітовою шириною, найменша e така, що x + e> x є e = 2 26 - 52 = 2 -26 . Іншими словами, найменше представницьке число, що перевищує x, становить x + 2 -26, тоді як результат sqrt (x 2 + 1) становить не більше x + 2 -27 . Оскільки режим закруглення IEEE-754 за замовчуванням є круглим до найближчого; рівний рівний зв'язок, він завжди буде круглим до x, а ніколи до x + 2 -26 (де розрив на краватку дійсно актуальний лише для x = 67,108,864, якщо взагалі. Будь-яке велике число округлятиме до x незалежно).


C ++, 75 000 000+

Нагадаємо, що 3 2 + 4 2 = 5 2 . Це означає, що точка (4, 3) лежить на колі радіуса 5, орієнтованому навколо початку. На насправді, для всіх цілих п , (4n, 3n) лежить на такий окружності радіуса 5n . Для досить великих n (а саме таких, що 5n> = 2 26 ), ми вже знаємо хибнопозитивне для всіх точок цього кола: (5n, 1) . Чудово! Ось ще 27 797 402/5 безкоштовних помилково-позитивних пар прямо там! Але навіщо зупинятися тут? (3, 4, 5) - не єдина така триплета.

Ця програма виглядає для всіх позитивних цілих триплетів (а, б, в) такі , що через 2 + B 2 = з 2 , і підраховує помилкові спрацьовування в цій моді. Він потрапляє до 70 000 000 хибнопозитивних результатів досить швидко, але потім значно сповільнюється в міру зростання числа.

Компілювати з g++ flspos.cpp -oflspos -std=c++11 -msse2 -mfpmath=sse -O3. Щоб перевірити результати, додайте -DVERIFY(це буде помітно повільніше.)

Бігайте з flspos. Будь-який аргумент командного рядка для багатослівного режиму.

#include <cstdio>
#include <cmath>
#include <cfloat>

using namespace std;

/* Make sure we actually work with 64-bit precision */
#if defined(VERIFY) && FLT_EVAL_METHOD != 0 && FLT_EVAL_METHOD != 1
#   error "invalid FLT_EVAL_METHOD (did you forget `-msse2 -mfpmath=sse'?)"
#endif

template <typename T> inline long long sqr(T x) { return 1ll * x * x; }
template <typename T>
T gcd(T a, T b) { while (b) { T t = a; a = b; b = t % b; } return a; }

int main(int argc, const char* argv[]) {
    const bool verbose = argc > 1;

    const int min = 1 << 26;
    const int max = sqrt(1ll << 53);

    int found = 0;
    auto add = [=, &found] (int x1, int y1, int x2, int y2) {
#ifdef VERIFY
        auto n1 = sqr(x1) + sqr(y1), n2 = sqr(x2) + sqr(y2);
        if (n1 == n2 || sqrt(n1) != sqrt(n2)) {
            fprintf(stderr, "Wrong false-positive: (%d, %d) (%d, %d)\n",
                    x1, y1, x2, y2);
            return;
        }
#endif
        if (verbose) printf("(%d, %d) (%d, %d)\n", x1, x2, y1, y2);
        ++found;
    };

    for (int x = min; x <= max; ++x) add(x, 0,    x, 1);

    for (int a = 1; a < max; ++a) {
        auto a2b2 = sqr(a) + 1;
        for (int b = 1; b <= a; a2b2 += 2 * b + 1, ++b) {
            int c = sqrt(a2b2);
            if (a2b2 == sqr(c) && gcd(a, b) == 1) {
                int max_c = max / c;
                for (int n = (min + c - 1) / c; n <= max_c; ++n)
                    add(n * a, n * b,    n * c, 1);
            }
        }

        if (a % 512 == 0) printf("%d\n", found), fflush(stdout);
    }

    printf("%d\n", found);
}

Так, це ефективна стратегія. Я думав, що для того, 2**53щоб виключати це, була обрана межа, але, мабуть, ні.
xnor

Смішно, як працює кожне число в цьому діапазоні без жодного примірника квадратних коренів x ^ 2 та x ^ 2 + 1, що падають на різні сторони цілого числа + 1/2.
feersum

@xnor Межа була обрана для того, щоб величина квадрата була точно представленою у 64-бітових поплавках.
Мартін Ендер

Гей, це працює, кого хвилює? ;) Ви маєте на увазі, що програма повинна нараховуватись у фіксованому циклі чи фактично перевіряти результати?
Ell

@MartinButtner О, бачу. Здається, що нижня межа - це сума, поділена на квадратний корінь на 2. Я евристично розумію, чому такі числа повинні працювати, але мені також цікаво, чому працює кожен.
xnor

4

C ++ 11 - 100,993,667

EDIT: Нова програма.

Стара використовувала занадто багато пам’яті. Це вдвічі зменшує використання пам'яті, використовуючи гігантський векторний масив замість хеш-таблиці. Крім того, він видаляє випадкову кришку нитки.

   /* by feersum  2014/9
   http://codegolf.stackexchange.com/questions/37627/false-positives-on-an-integer-lattice */
#include <iostream>
#include <cmath>
#include <cstdlib>
#include <cstring>
#include <functional>
#include <vector>
using namespace std;
#define ul unsigned long long

#define K const



#define INS(A)   { bool already = false; \
    for(auto e = res[A.p[0][0]].end(), it = res[A.p[0][0]].begin(); it != e; ++it) \
        if(A.p[0][1] == it->y1 && A.p[1][0] == it->x2 && A.p[1][1] == it->y2) { \
            already = true; \
            break; } \
    if(!already) res[A.p[0][0]].push_back( {A.p[0][1], A.p[1][0], A.p[1][1]} ), ++n; }

#define XMAXMIN (1<<26)

struct ints3 {
    int y1, x2, y2;
};


struct pparm {
    int a,b,c,d;
    int E[4];
    pparm(int A,int B,int C, int D):
        E{B*B+D*D,A*B+C*D,A*A+C*C+2*(B+D),A+C}
    {
        a=A;b=B;c=C;d=D;
    }

};

struct ans {
    int p[2][2];

};
ostream&operator<<(ostream&o, ans&a)
{
    o<<'('<<a.p[0][0]<<','<<a.p[0][1]<<"),("<<a.p[1][0]<<','<<a.p[1][1]<<')'<<endl;
    return o;
}



vector<ints3> res[XMAXMIN];

bool print;
int n;

void gen(K pparm&p1, K pparm&p2)
{
#ifdef DBUG
    for(int i=0;i<2;i++){
    K pparm&p=i?p2:p1;
    cout<<' '<<p.a<<' '<<p.b<<' '<<p.c<<' '<<p.d<<' ';}
    cout<<endl;
#endif

    for(ul x = 0; ; ++x) {
        ans a;
        ul s[2];
        for(int i = 0; i < 2; i++) {
            K pparm &p = i?p2:p1;
            int *pt = a.p[i];
            pt[0] = p.b+x*(p.a+x);
            pt[1] = p.d+x*(p.c+x);
            s[i] = (ul)pt[0]*pt[0] + (ul)pt[1]*pt[1];
        }
        if(*s >> 53)
            break;
if(s[1] - s[0] != 1)
exit(4);

        if(sqrt(s[0]) == sqrt(s[1])) {
             for(int i = 0; i < 2; i++)
                if(a.p[i][0] > a.p[i][1])
                    swap(a.p[i][0], a.p[i][1]);
            if(a.p[0][0] > a.p[0][1])
                for(int i = 0; i < 2; i++)
                    swap(a.p[0][i], a.p[1][i]);
            INS(a)
        }
    }
}



int main(int ac, char**av)
{
    for(int i = 1; i < ac; i++) {
        print |= !strcmp(av[1], "-P");
    }


    #define JMAX 43000000
    for(ul j = 0; j < JMAX; j++) {
        pparm p1(-~j,j,~-j,0),p2(j,1,j,j);
        gen(p1,p2);
        if(!print && !(j%1024))
#ifdef DBUG
            cout<<j<<' ',
#endif
            cout<<n<<endl;

    }
    if(print) 
        for(vector<ints3>& v: res)
            for(ints3& i: v)
                printf("(%d,%d),(%d,%d)\n", &v - res, i.y1, i.x2, i.y2);

    return 0;
}

Запустіть з -Pаргументом, щоб роздрукувати точки, а не їх кількість.

Для мене це займає менше 2 хвилин в режимі підрахунку і близько 5 хвилин з друком, направленим на файл (~ 4 Гб), тому він не зовсім став обмеженим вводу / виводу.

Моя оригінальна програма була акуратною, але я кинув більшу частину її, оскільки вона могла створити лише на порядок 10 ^ 5 результатів. Що потрібно зробити, це шукати параметризації форми (x ^ 2 + Ax + B, x ^ 2 + Cx + D), (x ^ 2 + ax + b, x ^ 2 + cx + d), так що для будь-якого x, (x ^ 2 + Ax + B) ^ 2 + (x ^ 2 + Cx + D) ^ 2 = (x ^ 2 + ax + b) ^ 2 + (x ^ 2 + cx + d) ^ 2 + 1. Коли він знайшов такий набір параметрів {a, b, c, d, A, B, C, D}, він перейшов до перевірки всіх значень x під максимумом. Переглядаючи мій вихід налагодження з цієї програми, я помітив певну параметризацію параметризації параметризації, яка дозволила мені легко створювати багато чисел. Я вирішив не друкувати номери Елла, оскільки мав багато своїх власних. Сподіваємось, зараз хтось не виведе обидва наші набори номерів і претендуватиме на перемогу :)

 /* by feersum  2014/9
   http://codegolf.stackexchange.com/questions/37627/false-positives-on-an-integer-lattice */
    #include <iostream>
    #include <cmath>
    #include <cstdlib>
    #include <cstring>
    #include <functional>
    #include <unordered_set>
    #include <thread>
using namespace std;
#define ul unsigned long long

#define h(S) unordered_##S##set
#define P 2977953206964783763LL
#define K const

#define EQ(T, F)bool operator==(K T&o)K{return!memcmp(F,o.F,sizeof(F));}

struct pparm {
    int a,b,c,d;
    int E[4];
    pparm(int A,int B,int C, int D):
        E{B*B+D*D,A*B+C*D,A*A+C*C+2*(B+D),A+C}
    {
        a=A;b=B;c=C;d=D;
    }
    EQ(pparm,E)
};

struct ans {
    int p[2][2];
    EQ(ans,p)
};
ostream&operator<<(ostream&o, ans&a)
{
    o<<'('<<a.p[0][0]<<','<<a.p[0][1]<<"),("<<a.p[1][0]<<','<<a.p[1][1]<<')'<<endl;
    return o;
}

#define HASH(N,T,F) \
struct N { \
    size_t operator() (K T&p) K { \
        size_t h = 0; \
        for(int i = 4; i--; ) \
            h=h*P+((int*)p.F)[i]; \
        return h; \
    }};

#define INS(r, a) { \
    bool new1 = r.insert(a).second; \
    n += new1; \
    if(print && new1) \
        cout<<a; }

HASH(HA,ans,p)

bool print;
int n;

void gen(h()<ans,HA>&r, K pparm&p1, K pparm&p2)
{
#ifdef DBUG
    for(int i=0;i<2;i++){
    K pparm&p=i?p2:p1;
    cout<<' '<<p.a<<' '<<p.b<<' '<<p.c<<' '<<p.d<<' ';}
    cout<<endl;
#endif

    for(ul x = 0; ; ++x) {
        ans a;
        ul s[2];
        for(int i = 0; i < 2; i++) {
            K pparm &p = i?p2:p1;
            int *pt = a.p[i];
            pt[0] = p.b+x*(p.a+x);
            pt[1] = p.d+x*(p.c+x);
            s[i] = (ul)pt[0]*pt[0] + (ul)pt[1]*pt[1];
        }
        if(*s >> 53)
            break;
if(s[1] - s[0] != 1)
exit(4);

        if(sqrt(s[0]) == sqrt(s[1])) {
             for(int i = 0; i < 2; i++)
                if(a.p[i][0] > a.p[i][1])
                    swap(a.p[i][0], a.p[i][1]);
            INS(r,a)
        }
    }
    //if(!print) cout<<n<<endl;
}

void endit()
{
    this_thread::sleep_for(chrono::seconds(599));
    exit(0);
}

int main(int ac, char**av)
{
    bool kill = false;
    for(int i = 1; i < ac; i++) {
        print |= ac>1 && !stricmp(av[1], "-P");
        kill |= !stricmp(av[i], "-K");
    }

    thread KILLER;
    if(kill)
        KILLER = thread(endit);

    h()<ans, HA> res;
    res.reserve(1<<27);

    #define JMAX 43000000
    for(ul j = 0; j < JMAX; j++) {
        pparm p1(-~j,j,~-j,0),p2(j,1,j,j);
        gen(res,p1,p2);
        if(!print && !(j%1024))
#ifdef DBUG
            cout<<j<<' ',
#endif
            cout<<n<<endl;

    }
    exit(0);
}

Я отримую купу помилок компілятора: pastebin.com/enNcY9fx Будь-яка підказка, що відбувається?
Мартін Ендер

@Martin Не маю ідеї ... Я скопіював свою публікацію у файл, складений на ноутбуці Windows 8 з однаковими комутаторами. Добре працює для мене. Яку версію gcc у вас є?
feersum

Btw, якщо вони викликають помилки, ви можете просто видалити всі пов’язані з потоком біти, які абсолютно непотрібні. Вони щось роблять лише в тому випадку, якщо ви використовуєте опцію "-K", яка не потрібна.
feersum

g++ (GCC) 4.8.1. Гаразд, я видалив бітові шматочки, але це все ще stricmpчомусь не розпізнається .
Мартін Ендер

1
На даний момент у мене відбувається надто багато інших речей, тому я розповім вам свою ідею, щоб покращити ваш підхід. Якщо радіус розташований у верхньому кінці діапазону, ви також можете зіткнутися між квадратом радіуса, який відрізняється на 2.
Пітер Тейлор

1

Java, Bresenham-esque

Евристично я розраховую отримати більше зіткнень, починаючи з більш широкого кінця кільця. Я очікував на деяке покращення, зробивши по одному скануванню для кожного зіткнення, записуючи значення якого surplusміж 0і r2max - r2включно, але в моєму тестуванні це виявилося повільніше, ніж ця версія. Аналогічно намагається використовувати один int[]буфер, а не багато створення двоелементних масивів і списків. Оптимізація продуктивності справді дивний звір.

Запустіть аргумент командного рядка для виведення пар і без простих підрахунків.

import java.util.*;

public class CodeGolf37627 {
    public static void main(String[] args) {
        final int M = 144;
        boolean[] possible = new boolean[M];
        for (int i = 0; i <= M/2; i++) {
            for (int j = 0; j <= M/2; j++) {
                possible[(i*i+j*j)%M] = true;
            }
        }

        long count = 0;
        double sqrt = 0;
        long r2max = 0;
        List<int[]> previousPoints = null;
        for (long r2 = 1L << 53; ; r2--) {
            if (!possible[(int)(r2 % M)]) continue;

            double r = Math.sqrt(r2);
            if (r != sqrt) {
                sqrt = r;
                r2max = r2;
                previousPoints = null;
            }
            else {
                if (previousPoints == null) previousPoints = findLatticePointsBresenham(r2max, (int)r);

                if (previousPoints.size() == 0) {
                    r2max = r2;
                    previousPoints = null;
                }
                else {
                    List<int[]> points = findLatticePointsBresenham(r2, (int)r);
                    for (int[] p1 : points) {
                        for (int[] p2 : previousPoints) {
                            if (args.length > 0) System.out.format("(%d, %d) (%d, %d)\n", p1[0], p1[1], p2[0], p2[1]);
                            count++;
                        }
                    }
                    previousPoints.addAll(points);
                    System.out.println(count);
                }
            }
        }
    }

    // Surprisingly, this seems to be faster than doing one scan for all two or three r2s.
    private static List<int[]> findLatticePointsBresenham(long r2, long r) {
        List<int[]> rv = new ArrayList<int[]>();
        // Require 0 = y = x
        long x = r, y = 0, surplus = r2 - r * r;
        while (y <= x) {
            if (surplus == 0) rv.add(new int[]{(int)x, (int)y});

            // Invariant: surplus = r2 - x*x - y*y >= 0
            y++;
            surplus -= 2*y - 1;
            if (surplus < 0) {
                x--;
                surplus += 2*x + 1;
            }
        }

        return rv;
    }
}

1

Ява - 27 817 255

Більшість із них - це те саме, що показує Елл , а решта - на основі (j,0) (k,l). Для кожного jя проходжу кілька квадратів назад і перевіряю, чи залишок дає помилковий позитив. В основному це займає весь час, лише 25к (приблизно 0,1%) виграш за справедливий (j,0) (j,1), але виграш - це виграш.

Це закінчиться за десять хвилин на моїй машині, але я не знаю, що у вас є. Тому що причини, якщо це не закінчиться до закінчення часу, це матиме різко гірший бал. У такому випадку ви можете налаштувати дільник на лінії 8, щоб він закінчився вчасно (це просто визначає, наскільки далеко він ходить для кожного j). Для деяких різних дільників бали:

11    27817255 (best on OPs machine)
10    27818200
8     27820719
7     27822419 (best on my machine)

Щоб увімкнути вихід для кожного матчу (і, боже, це повільно, якщо ви робите), просто пров'яжіть рядки 10 та 19.

public class FalsePositive {
    public static void main(String[] args){
        long j = 67108864;
        long start = System.currentTimeMillis();
        long matches=0;
        while(j < 94906265 && System.currentTimeMillis()-start < 599900){
            long jSq = j*j;
            long limit = (long)Math.sqrt(j)/11; // <- tweak to fit inside 10 minutes for best results
            matches++; // count an automatic one for (j,0)(j,1)
            //System.out.println("("+j+",0) ("+j+",1)");        
            for(int i=1;i<limit;i++){
                long k = j-i;
                long kSq = k*k;
                long l = (long)Math.sqrt(jSq-kSq);
                long lSq = l*l;
                if(kSq+lSq != jSq){
                    if(Math.sqrt(kSq+lSq)==Math.sqrt(jSq)){
                        matches++;
                        //System.out.println("("+j+",0) ("+k+","+l+")");        
                    }
                }
            }
            j++;
        }
        System.out.println("\n"+matches+" Total matches, got to j="+j);
    }
}

Для довідки, першими 20 виходами, які він дає (для дільника = 7, виключаючи (j,0)(j,1)типи), є:

(67110083,0) (67109538,270462)
(67110675,0) (67109990,303218)
(67111251,0) (67110710,269470)
(67111569,0) (67110668,347756)
(67112019,0) (67111274,316222)
(67112787,0) (67111762,370918)
(67115571,0) (67115518,84346)
(67117699,0) (67117698,11586)
(67117971,0) (67117958,41774)
(67120545,0) (67120040,260368)
(67121043,0) (67120118,352382)
(67122345,0) (67122320,57932)
(67122449,0) (67122444,25908)
(67122633,0) (67122328,202348)
(67122729,0) (67121972,318784)
(67122849,0) (67122568,194224)
(67124195,0) (67123818,224970)
(67125201,0) (67125172,62396)
(67125705,0) (67124632,379540)
(67126195,0) (67125882,204990)

0

Юлія, 530 хибнопозитивних

Ось дуже наївний пошук грубої сили, який ви можете розглядати як еталонну реалізацію.

num = 0
for i = 60000000:-1:0
    for j = i:-1:ifloor(0.99*i)
        s = i*i + j*j
        for x = ifloor(sqrt(s/2)):ifloor(sqrt(s))
            min_y = ifloor(sqrt(s - x*x))
            max_y = min_y+1
            for y = min_y:max_y
                r = x*x + y*y
                if r != s && sqrt(r) == sqrt(s)
                    num += 1
                    if num % 10 == 0
                        println("Found $num pairs")
                    end
                    #@printf("(i,j) = (%d,%d); (x,y) = (%d,%d); s = %d, r = %d\n", i,j,x,y,s,r)
                end
            end
        end
    end
end

Ви можете роздрукувати пари (та їх точну квадратичну величину), коментуючи @printfлінію.

В основному це починає пошук x = y = 6e7першої пари координат і сканує приблизно 1% шляху до осі x перед тим, як зменшити x. Потім для кожної такої пари координат він перевіряє всю дугу однакової величини (округлення вгору і вниз) на предмет зіткнення.

Код передбачає, що він працює в 64-бітній системі, так що цілі цілі числа і типи з плаваючою комою є 64-розрядними (якщо ні, ви можете їх створити int64()і з float64()конструкторами).

Це дає мізерні 530 результатів.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.