Найшвидший приблизний спільний дільник


13

Огляд

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

Розмір числа бере участь буде параметризованих параметром складності, l. Ваша мета - вирішити проблему якнайбільше lза менше 1 хвилини.


Налаштування

У заданій проблемі буде секретне число p, яке буде випадковим l^2( l*l) бітовим числом. Буде два множника, q1, q2які будуть випадковими l^3бітовими числами, і буде два зміщення r1, r2, які будуть випадковими lбітовими числами.

Вхід до вашої програми буде x1, x2визначено як:

x1 = p * q1 + r1
x2 = p * q2 + r2

Ось програма для генерації тестових випадків на Python:

from random import randrange
from sys import argv

l = int(argv[1])

def randbits(bits):
    return randrange(2 ** (bits - 1), 2 ** bits)

p = randbits(l ** 2)
print(p)
for i in range(2):
    q_i = randbits(l ** 3)
    r_i = randbits(l)
    print(q_i * p + r_i)

Перший рядок виводу є можливим рішенням, а другий та третій рядки - це вхід, який буде надано вашій програмі.


Ваша програма

З огляду на x1, x2і lви повинні знайти l^2розрядне число p'таких , що x1 % p'і x2 % p'обидва lбітових чисел. pзавжди працюватиме, хоча можуть бути й інші можливості. Ось функція перевірки рішення:

def is_correct(x1, x2, l, p_prime):
    p_prime_is_good = p_prime >> (l**2 - 1) and not p_prime >> l ** 2
    x1_is_good = (x1 % p_prime) >> (l-1) and not (x1 % p_prime) >> l
    x2_is_good = (x2 % p_prime) >> (l-1) and not (x2 % p_prime) >> l
    return bool(p_prime_is_good and x1_is_good and x2_is_good)

Приклад

Припустимо l, що 3. Програма генератора вибирає 9-розрядне число для p, яке в даному випадку є 442. Генератор вибирає два 3бітні числа для r1, r2, які є 4, 7. Генератор вибирає два 27бітні числа для q1, q2, які є 117964803, 101808039. Через ці варіанти x1, x2є 52140442930, 44999153245.

Вашій програмі буде надано 52140442930, 44999153245як вхід, і вона повинна виводити 9-бітове число (в діапазоні [256, 511]) таким чином, що 52140442930і 44999153245за модулем це число дає 3 бітні числа (в діапазоні [4, 7]). 442в цьому випадку є єдиним таким значенням, тому ваша програма повинна виводити 442.


Більше прикладів

l = 2
x1 = 1894
x2 = 2060
p = 11
No other p'.

l = 3
x1 = 56007668599
x2 = 30611458895
p = 424
No other p'.

l = 6
x1 = 4365435975875889219149338064474396898067189178953471159903352227492495111071
x2 = 6466809655659049447127736275529851894657569985804963410176865782113074947167
p = 68101195620
I don't know whether there are other p'.

l = 12
x1 = 132503538560485423319724633262218262792296147003813662398252348727558616998821387759658729802732555377599590456096450977511271450086857949046098328487779612488702544062780731169071526325427862701033062986918854245283037892816922645703778218888876645148150396130125974518827547039720412359298502758101864465267219269598121846675000819173555118275197412936184329860639224312426860362491131729109976241526141192634523046343361089218776687819810873911761177080056675776644326080790638190845283447304699879671516831798277084926941086929776037986892223389603958335825223
x2 = 131643270083452525545713630444392174853686642378302602432151533578354175874660202842105881983788182087244225335788180044756143002547651778418104898394856368040582966040636443591550863800820890232349510212502022967044635049530630094703200089437589000344385691841539471759564428710508659169951391360884974854486267690231936418935298696990496810984630182864946252125857984234200409883080311780173125332191068011865349489020080749633049912518609380810021976861585063983190710264511339441915235691015858985314705640801109163008926275586193293353829677264797719957439635
p = 12920503469397123671484716106535636962543473
I don't know whether there are other p'.

l = 12
x1 = 202682323504122627687421150801262260096036559509855209647629958481910539332845439801686105377638207777951377858833355315514789392768449139095245989465034831121409966815913228535487871119596033570221780568122582453813989896850354963963579404589216380209702064994881800638095974725735826187029705991851861437712496046570494304535548139347915753682466465910703584162857986211423274841044480134909827293577782500978784365107166584993093904666548341384683749686200216537120741867400554787359905811760833689989323176213658734291045194879271258061845641982134589988950037
x2 = 181061672413088057213056735163589264228345385049856782741314216892873615377401934633944987733964053303318802550909800629914413353049208324641813340834741135897326747139541660984388998099026320957569795775586586220775707569049815466134899066365036389427046307790466751981020951925232623622327618223732816807936229082125018442471614910956092251885124883253591153056364654734271407552319665257904066307163047533658914884519547950787163679609742158608089946055315496165960274610016198230291033540306847172592039765417365770579502834927831791804602945514484791644440788
p = 21705376375228755718179424140760701489963164

Оцінка балів

Як було сказано вище, оцінка вашої програми є найвищою, lяку програма завершує за 1 хвилину. Більш конкретно, ваша програма буде працювати в 5 випадкових екземплярах з цим l, і вона повинна вивести правильну відповідь на всі 5, із середнім часом менше 1 хвилини. Оцінка програми буде найвищою, на lяку вона вдається. Викрадач буде середнім часом на це l.

Щоб дати вам уявлення про те, до яких балів слід прагнути, я написав дуже просту розв'язку. Він отримав оцінку 5. Я написав набагато більш шалені рішення. Він отримав оцінку 12 або 13, залежно від удачі.


Деталі

Для ідеальної порівняльності між відповідями, я піду час на своєму ноутбуці, щоб дати канонічні оцінки. Я також буду запускати однакові випадково вибрані екземпляри у всіх поданнях, щоб дещо полегшити удачу. Мій ноутбук має 4 процесора, процесор i5-4300U при 1,9 ГГц, 7,5 г оперативної пам’яті.

Не соромтеся розміщувати попередній бал, виходячи із власних термінів, просто дайте зрозуміти, тимчасовий він чи канонічний.


Нехай виграє найшвидша програма!


Чи наближення має бути найближчим?
Yytsi

@TuukkaX Будь-яке l^2бітове число, яке lне є фактором обох чисел, працює. Однак, як правило, лише один.
isaacg

Ось дисертація з деякими ідеями алгоритму: tigerprints.clemson.edu/cgi/… . Особливо приємним і простим є той у розділі 5.1.1
isaacg

I5-4300U має 2 ядра (4 потоку), а не 4 ядра.
Андерс Касеорг

Відповіді:


3

Іржа, L = 13

src/main.rs

extern crate gmp;
extern crate rayon;

use gmp::mpz::Mpz;
use gmp::rand::RandState;
use rayon::prelude::*;
use std::cmp::max;
use std::env;
use std::mem::swap;

// Solver

fn solve(x1: &Mpz, x2: &Mpz, l: usize) -> Option<Mpz> {
    let m = l*l - 1;
    let r = -1i64 << l-2 .. 1 << l-2;
    let (mut x1, mut x2) = (x1 - (3 << l-2), x2 - (3 << l-2));
    let (mut a1, mut a2, mut b1, mut b2) = (Mpz::one(), Mpz::zero(), Mpz::zero(), Mpz::one());
    while !x2.is_zero() &&
        &(max(a1.abs(), b1.abs()) << l-2) < &x1 &&
        &(max(a2.abs(), b2.abs()) << l-2) < &x2
    {
        let q = &x1/&x2;
        swap(&mut x1, &mut x2);
        x2 -= &q*&x1;
        swap(&mut a1, &mut a2);
        a2 -= &q*&a1;
        swap(&mut b1, &mut b2);
        b2 -= &q*&b1;
    }
    r.clone().into_par_iter().map(|u| {
        let (mut y1, mut y2) = (&x1 - &a1*u + (&b1 << l-2), &x2 - &a2*u + (&b2 << l-2));
        for _ in r.clone() {
            let d = Mpz::gcd(&y1, &y2);
            if d.bit_length() >= m {
                let d = d.abs();
                let (mut k, k1) = (&d >> l*l, &d >> l*l-1);
                while k < k1 {
                    k += 1;
                    if (&d%&k).is_zero() {
                        return Some(&d/&k)
                    }
                }
            }
            y1 -= &b1;
            y2 -= &b2;
        }
        None
    }).find_any(|o| o.is_some()).unwrap_or(None)
}

// Test harness

fn randbits(rnd: &mut RandState, bits: usize) -> Mpz {
    rnd.urandom(&(Mpz::one() << bits - 1)) + (Mpz::one() << bits - 1)
}

fn gen(rnd: &mut RandState, l: usize) -> (Mpz, Mpz, Mpz) {
    let p = randbits(rnd, l*l);
    (randbits(rnd, l*l*l)*&p + randbits(rnd, l),
     randbits(rnd, l*l*l)*&p + randbits(rnd, l),
     p)
}

fn is_correct(x1: &Mpz, x2: &Mpz, l: usize, p_prime: &Mpz) -> bool {
    p_prime.bit_length() == l*l &&
        (x1 % p_prime).bit_length() == l &&
        (x2 % p_prime).bit_length() == l
}

fn random_test(l: usize, n: usize) {
    let mut rnd = RandState::new();  // deterministic seed
    for _ in 0..n {
        let (x1, x2, p) = gen(&mut rnd, l);
        println!("x1 = {}", x1);
        println!("x2 = {}", x2);
        println!("l = {}", l);
        println!("p = {}", p);
        println!("x1 % p = {}", &x1 % &p);
        println!("x2 % p = {}", &x2 % &p);
        assert!(is_correct(&x1, &x2, l, &p));
        let p_prime = solve(&x1, &x2, l).expect("no solution found!");
        println!("p' = {}", p_prime);
        assert!(is_correct(&x1, &x2, l, &p_prime));
        println!("correct");
    }
}

// Command line interface

fn main() {
    let args = &env::args().collect::<Vec<_>>();
    if args.len() == 4 && args[1] == "--random" {
        if let (Ok(l), Ok(n)) = (args[2].parse(), args[3].parse()) {
            return random_test(l, n);
        }
    }
    if args.len() == 4 {
        if let (Ok(x1), Ok(x2), Ok(l)) = (args[1].parse(), args[2].parse(), args[3].parse()) {
            match solve(&x1, &x2, l) {
                None => println!("no solution found"),
                Some(p_prime) => println!("{}", p_prime),
            }
            return;
        }
    }
    println!("Usage:");
    println!("    {} --random L N", args[0]);
    println!("    {} X1 X2 L", args[0]);
}

Cargo.toml

[package]
name = "agcd"
version = "0.1.0"
authors = ["Anders Kaseorg <andersk@mit.edu>"]

[dependencies]
rayon = "0.7.1"
rust-gmp = "0.5.0"

Бігти

cargo build --release
target/release/agcd 56007668599 30611458895 3
target/release/agcd --random 13 5

Офіційний результат l = 13, із середнім часом 41,53s. l = 14 займає в середньому трохи більше 2 м.
isaacg

2

Математика, L = 5

ось швидке 5 рішення

(l = #3;
a = #1 - Range[2^(l - 1), 2^l];
b = #2 - Range[2^(l - 1), 2^l];
Last@Intersection[
Flatten[Table[Select[Divisors@a[[i]], # < 2^l^2 &], {i, Length@a}],
 1],
Flatten[Table[Select[Divisors@b[[i]], # < 2^l^2 &], {i, Length@b}],
 1]]
) &

Введення
[x1, x2, L]

для того, щоб хтось міг це перевірити, ось генератор ключів

l = 5;
a = RandomInteger[{2^(l^2 - 1), 2^(l^2)}]
b = RandomInteger[{2^(l^3 - 1), 2^(l^3)}];
c = RandomInteger[{2^(l - 1), 2^l - 1}];
f = RandomInteger[{2^(l^3 - 1), 2^(l^3)}];
g = RandomInteger[{2^(l - 1), 2^l - 1}];
x = a*b + c
y = a*f + g

виберіть значення L run, код, і ви отримаєте три числа.
помістіть останні два разом з L як вхідні дані, і ви повинні отримати перше


Я перевірив це рішення як результат l = 5. Я встигну, якщо терміни знадобляться як краватка.
isaacg

1

Математика, L = 12

ClearAll[l, a, b, k, m];
(l = #3;
a = #1 - Range[2^(l - 1), 2^l];
b = #2 - Range[2^(l - 1), 2^l];
k = Length@a;
m = Length@b;
For[i = 1, i <= k, i++, 
For[j = 1, j <= m, j++, If[2^(l^2 - 1) < GCD[a[[i]], b[[j]]],
 If[GCD[a[[i]], b[[j]]] > 2^l^2, 
  Print@Max@
    Select[Divisors[GCD[a[[i]], b[[j]]]], 
     2^(l^2 - 1) < # < 2^l^2 &]; Abort[], 
  Print[GCD[a[[i]], b[[j]]]];
  Abort[]]]]]) &

вхід [x1, x2, L]

для того, щоб хтось міг це перевірити, ось генератор ключів

l = 12;
a = RandomInteger[{2^(l^2 - 1), 2^(l^2)}]
b = RandomInteger[{2^(l^3 - 1), 2^(l^3)}];
c = RandomInteger[{2^(l - 1), 2^l - 1}];
f = RandomInteger[{2^(l^3 - 1), 2^(l^3)}];
g = RandomInteger[{2^(l - 1), 2^l - 1}];
x = a*b + c
y = a*f + g

виберіть значення L, запустіть код і отримаєте три числа.
помістіть останні два разом з L як вхідні дані, і ви повинні отримати перше


Коли я тестував це l = 12, це дало неправильний результат. Зокрема, діючим дільником було 146-бітове число, яке занадто велике. Я думаю, вам знадобиться лише невелика зміна, щоб виправити це.
isaacg

Останній приклад я додав невдалий випадок. Ваш вирішальник дав відповідь, рівну 3 * p, що було трохи завелико. Дивлячись на ваш код, схоже, ви лише перевіряєте, чи має ваш вихід не менше l^2бітів, але ви також повинні перевірити, чи має він максимум l^2біт.
isaacg

У тому ж тестовому випадку, який не вдався раніше, він все ще не працює. Я не надто знайомий з Mathematica, але це виглядало так, що воно вийшло без результатів. Я думаю, що проблема полягає в тому, що це пошук занадто великого фактора, а потім замість того, щоб ділити фактор у потрібному діапазоні, він просто пропускає його.
isaacg


Офіційний бал - L = 12, середній час - 52,7 с. Молодці!
isaacg

1

Пітон, L = 15, середній час 39,9с

from sys import argv
from random import seed, randrange

from gmpy2 import gcd, mpz
import gmpy2

def mult_buckets(buckets):
    if len(buckets) == 1:
        return buckets[0]
    new_buckets = []
    for i in range(0, len(buckets), 2):
        if i == len(buckets) - 1:
            new_buckets.append(buckets[i])
        else:
            new_buckets.append(buckets[i] * buckets[i+1])
    return mult_buckets(new_buckets)


def get_products(xs, l):
    num_buckets = 1000
    lower_r = 1 << l - 1
    upper_r = 1 << l
    products = []
    bucket_size = (upper_r - lower_r)//num_buckets + 1
    for x in xs:
        buckets = []
        for bucket_num in range(num_buckets):
            product = mpz(1)
            for r in range(lower_r + bucket_num * bucket_size,
                           min(upper_r, lower_r + (bucket_num + 1) * bucket_size)):
                product *= x - mpz(r)
            buckets.append(product)
        products.append(mult_buckets(buckets))
    return products

def solve(xs, l):
    lower_r = 2**(l - 1)
    upper_r = 2**l
    lower_p = 2**(l**2 - 1)
    upper_p = 2**(l**2)

    products = get_products(xs, l)
    overlap = gcd(*products)
    candidate_lists = []
    for x in xs:
        candidates = []
        candidate_lists.append(candidates)
        for r in range(lower_r, upper_r):
            common_divisor = gcd(x-r, overlap)
            if common_divisor >= lower_p:
                candidates.append(common_divisor)
    to_reduce = []
    for l_candidate in candidate_lists[0]:
        for r_candidate in candidate_lists[1]:
            best_divisor = gcd(l_candidate, r_candidate)
            if lower_p <= best_divisor < upper_p:
                return best_divisor
            elif best_divisor > upper_p:
                to_reduce.append(best_divisor)
    for divisor in to_reduce:
        cutoff = divisor // lower_p
        non_divisors = []
        for sub_divisor in range(2, cutoff + 1):
            if any(sub_divisor % non_divisor == 0 for non_divisor in non_divisors):
                continue
            quotient, remainder = gmpy2.c_divmod(divisor, sub_divisor)
            if remainder == 0 and lower_p <= quotient < upper_p:
                return quotient
            if quotient < lower_p:
                break
            if remainder != 0:
                non_divisors.append(sub_divisor)

def is_correct(x1, x2, l, p_prime):
    p_prime_is_good = p_prime >> (l**2 - 1) and not p_prime >> l ** 2
    x1_is_good = (x1 % p_prime) >> (l-1) and not (x1 % p_prime) >> l
    x2_is_good = (x2 % p_prime) >> (l-1) and not (x2 % p_prime) >> l
    return bool(p_prime_is_good and x1_is_good and x2_is_good)

if __name__ == '__main__':
    seed(0)

    l = int(argv[1])
    reps = int(argv[2])

    def randbits(bits):
        return randrange(2 ** (bits - 1), 2 ** bits)

    for _ in range(reps):
        p = randbits(l ** 2)
        print("p = ", p)
        xs = []
        for i in range(2):
            q_i = randbits(l ** 3)
            print("q", i, "=", q_i)
            r_i = randbits(l)
            print("r", i, "=", r_i)
            x_i = q_i * p + r_i
            print("x", i, "=", x_i)
            xs.append(x_i)

        res = solve(xs, l)
        print("answer = ", res)
        print("Correct?", is_correct(xs[0], xs[1], l, res))

Цей код заснований на тому, що добуток x1 - r для всіх можливих значень r повинен бути кратним p, а добуток x2 - r також повинен бути рівним. Більша частина часу витрачається на отримання gcd двох продуктів, кожен з яких має близько 60 000 000 біт. Потім gcd, який має лише близько 250 000 біт, порівнюється з кожним із значень xr ще раз для вилучення p 'кандидатів. Якщо вони трохи завеликі, застосовується пробний поділ, щоб зменшити їх до відповідного діапазону.

Це засновано на бібліотеці gmpy2 для Python, яка є тонкою обгорткою для бібліотеки MP GNU, яка, зокрема, має дійсно гарну програму gcd.

Цей код є однопоточним.

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