E022. Эффективные 알고리즘ы факторизации

e-maxx algorithm original: C/C++ #algorithm #emaxx #math #number-theory
선택한 UI 언어에 맞게 문제 텍스트를 러시아어에서 번역합니다. 코드는 변경하지 않습니다.

Источник: e-maxx.ru/algo, страница PDF 67.

Здесь приведены реализации нескольких 알고리즘ов факторизации, каждый из которых по отдельности может работать как быстро, так и очень медленно, но в сумме они дают весьма быстрый метод. Описания этих методов не приводятся, тем более что они достаточно хорошо описаны в Интернете.

Метод Полларда p-1

Вероятностный тест, быстро даёт ответ далеко не для всех чисел. returns либо найденный делитель, либо 1, если делитель не был найден.

template <class T>

T pollard_p_1 (T n)

{ // параметры 알고리즘а, существенно влияют на производительность

и качество поиска

const T b = 13;

const T q[] = { 2, 3, 5, 7, 11, 13 };

// несколько попыток 알고리즘а

T a = 5 % n;

for (int j=0; j<10; j++)

{

// ищем такое a, которое взаимно просто с n

while (gcd (a, n) != 1)

{

mulmod (a, a, n);

a += 3;

a %= n;

}

// вычисляем a^M

for (size_t i = 0; i < sizeof q / sizeof q[0]; i++)

{

T qq = q[i];

T e = (T) floor (log ((double)b) / log ((double)qq));

T aa = powmod (a, powmod (qq, e, n), n);

if (aa == 0)

continue;

// проверяем, не найден ли ответ

T g = gcd (aa-1, n);

if (1 < g && g < n)
return g;

} }

// если ничего не нашли

return 1;

}

Метод Полларда "Ро"

Вероятностный тест, быстро даёт ответ далеко не для всех чисел. returns либо найденный делитель, либо 1, если делитель не был найден.

template <class T>

T pollard_rho (T n, unsigned iterations_count = 100000)

{

T

b0 = rand() % n,

b1 = b0,

g;

mulmod (b1, b1, n);

if (++b1 == n)

b1 = 0;

g = gcd (abs (b1 - b0), n);

for (unsigned count=0; count<iterations_count && (g == 1 || g ==

n); count++)

{

mulmod (b0, b0, n);

if (++b0 == n)

b0 = 0;

mulmod (b1, b1, n);

++b1;

mulmod (b1, b1, n);

if (++b1 == n)

b1 = 0;

g = gcd (abs (b1 - b0), n);

}

return g;

}

Метод Бента (модификация метода Полларда "Ро")

Вероятностный тест, быстро даёт ответ далеко не для всех чисел. returns либо найденный делитель, либо 1, если делитель не был найден.

template <class T>

T pollard_bent (T n, unsigned iterations_count = 19)

{

T

b0 = rand() % n,

b1 = (b0*b0 + 2) % n,

a = b1;

for (unsigned iteration=0, series_len=1;

iteration<iterations_count; iteration++, series_len*=2)

{

T g = gcd (b1-b0, n);

for (unsigned len=0; len<series_len && (g==1 && g==n); len++)

{

b1 = (b1*b1 + 2) % n;

g = gcd (abs(b1-b0), n);

}

b0 = a;

a = b1;

if (g != 1 && g != n)
return g;

}

return 1;

}

Метод Полларда Монте-Карло

Вероятностный тест, быстро даёт ответ далеко не для всех чисел. returns либо найденный делитель, либо 1, если делитель не был найден.

template <class T>

T pollard_monte_carlo (T n, unsigned m = 100)

{

T b = rand() % (m-2) + 2;

static std::vector<T> primes;

static T m_max;

if (primes.empty())

primes.push_back (3);

if (m_max < m)

{

m_max = m;

for (T prime=5; prime<=m; ++++prime)

{

bool is_prime = true;

for (std::vector<T>::const_iterator iter=primes.

begin(), end=primes.end();

iter!=end; ++iter)

{

T div = *iter;

if (div*div > prime)

break;

if (prime % div == 0)

{

is_prime = false;

break;

} }

if (is_prime)

primes.push_back (prime);

} }

T g = 1;

for (size_t i=0; i<primes.size() && g==1; i++)

{

T cur = primes[i];

while (cur <= n)

cur *= primes[i];

cur /= primes[i];

b = powmod (b, cur, n);

g = gcd (abs(b-1), n);

if (g == n)

g = 1;

}

return g;

}

Метод Ферма

Это стопроцентный метод, но он может работать очень медленно, если у числа есть маленькие делители. Поэтому запускать его стоит только после всех остальных методов.

template <class T, class T2>

T ferma (const T & n, T2 unused)

{

T2

x = sq_root (n),

y = 0,

r = x*x - y*y - n;

for (;;)
if (r == 0)
return x!=y ? x-y : x+y;

else

if (r > 0)

{

r -= y+y+1;

++y;

}

else

{

r += x+x+1;

++x;

} }

Тривиальное деление

Этот elementарный метод пригодится, чтобы сразу обрабатывать числа с очень маленькими делителями.

template <class T, class T2>

T2 prime_div_trivial (const T & n, T2 m)

{

// сначала проверяем тривиальные случаи

if (n == 2 || n == 3)
return 1;
if (n < 2)
return 0;
if (even (n))
return 2;

// генерируем простые от 3 до m

T2 pi;

const vector<T2> & primes = get_primes (m, pi);

// делим на все простые

for (std::vector<T2>::const_iterator iter=primes.begin(), end=primes.

end();

iter!=end; ++iter)

{

const T2 & div = *iter;

if (div * div > n)

break;

else

if (n % div == 0)
return div;

}

if (n < m*m)
return 1;
return 0;

}

Собираем всё вместе

Объединяем все методы в одной функции. Также функция использует тест на простоту, иначе 알고리즘ы факторизации могут работать очень долго. На예제, можно выбрать тест BPSW (читать статью по BPSW).

template <class T, class T2>

void factorize (const T & n, std::map<T,unsigned> & result, T2 unused)

{

if (n == 1)

;

else

// проверяем, не простое ли number

if (isprime (n))

++result[n];

else

// если number достаточно маленькое, то его

разлагаем простым перебором

if (n < 1000*1000)

{

T div = prime_div_trivial (n, 1000);

++result[div];

factorize (n / div, result, unused);

}

else

{

// number большое, запускаем на нем

알고리즘ы факторизации

T div;

// сначала идут быстрые 알고리즘ы Полларда

div = pollard_monte_carlo (n);

if (div == 1)

div = pollard_rho (n);

if (div == 1)

div = pollard_p_1 (n);

if (div == 1)

div = pollard_bent (n);

// придётся запускать 100%-ый 알고리즘 Ферма

if (div == 1)

div = ferma (n, unused);

// рекурсивно обрабатываем найденные множители

factorize (div, result, unused);

factorize (n / div, result, unused);

} }

Приложение

Скачать [5 КБ] исходник программы, которая использует все указанные методы факторизации и тест BPSW на простоту.

C# 해법

자동 초안, 제출 전 검토
using System;
using System.Collections.Generic;
using System.Linq;

public static class AlgorithmDraft
{
    // Auto-generated C# draft from the original e-maxx C/C++ listing. Review before production use.
    template <class T>
    T pollard_p_1 (T n)
    {
            // параметры алгоритма, существенно влияют на производительность
    и качество поиска
            const T b = 13;
            const T q[] = { 2, 3, 5, 7, 11, 13 };
            // несколько попыток алгоритма
            T a = 5 % n;
            for (int j=0; j<10; j++)
            {
                    // ищем такое a, которое взаимно просто с n
                    while (gcd (a, n) != 1)
                    {
                            mulmod (a, a, n);
                            a += 3;
                            a %= n;
                    }
                    // вычисляем a^M
                    for (size_t i = 0; i < sizeof q / sizeof q[0]; i++)
                    {
                            T qq = q[i];
                            T e = (T) floor (log ((double)b) / log ((double)qq));
                            T aa = powmod (a, powmod (qq, e, n), n);
                            if (aa == 0)
                                    continue;
                            // проверяем, не найден ли ответ
                            T g = gcd (aa-1, n);
                            if (1 < g && g < n)
                                    return g;
                    }
            }
            // если ничего не нашли
            return 1;
    }
    template <class T>
    T pollard_rho (T n, unsigned iterations_count = 100000)
    {
            T
                    b0 = rand() % n,
                    b1 = b0,
                    g;
            mulmod (b1, b1, n);
            if (++b1 == n)
                    b1 = 0;
            g = gcd (abs (b1 - b0), n);
            for (unsigned count=0; count<iterations_count && (g == 1 || g ==
    n); count++)
            {
                    mulmod (b0, b0, n);
                    if (++b0 == n)
                            b0 = 0;
                    mulmod (b1, b1, n);
                    ++b1;
                    mulmod (b1, b1, n);
                    if (++b1 == n)
                            b1 = 0;
                    g = gcd (abs (b1 - b0), n);
            }
            return g;
    }
    template <class T>
    T pollard_bent (T n, unsigned iterations_count = 19)
    {
            T
                    b0 = rand() % n,
                    b1 = (b0*b0 + 2) % n,
                    a = b1;
            for (unsigned iteration=0, series_len=1;
    iteration<iterations_count; iteration++, series_len*=2)
            {
                    T g = gcd (b1-b0, n);
                    for (unsigned len=0; len<series_len && (g==1 && g==n); len++)
                    {
                            b1 = (b1*b1 + 2) % n;
                            g = gcd (abs(b1-b0), n);
                    }
                    b0 = a;
                    a = b1;
                    if (g != 1 && g != n)
                            return g;
            }
            return 1;
    }
    template <class T>
    T pollard_monte_carlo (T n, unsigned m = 100)
    {
            T b = rand() % (m-2) + 2;
            static std::vector<T> primes;
            static T m_max;
            if (primes.empty())
                    primes.push_back (3);
            if (m_max < m)
            {
                    m_max = m;
                    for (T prime=5; prime<=m; ++++prime)
                    {
                            bool is_prime = true;
                            for (std::vector<T>::const_iterator iter=primes.
    begin(), end=primes.end();
                                    iter!=end; ++iter)
                            {
                                    T div = *iter;
                                    if (div*div > prime)
                                            break;
                                    if (prime % div == 0)
                                    {
                                            is_prime = false;
                                            break;
                                    }
                            }
                            if (is_prime)
                                    primes.push_back (prime);
                    }
            }
            T g = 1;
            for (size_t i=0; i<primes.size() && g==1; i++)
            {
                    T cur = primes[i];
                    while (cur <= n)
                            cur *= primes[i];
                    cur /= primes[i];
                    b = powmod (b, cur, n);
                    g = gcd (abs(b-1), n);
                    if (g == n)
                            g = 1;
            }
            return g;
    }
    template <class T, class T2>
    T ferma (const T & n, T2 unused)
    {
            T2
                    x = sq_root (n),
                    y = 0,
                    r = x*x - y*y - n;
            for (;;)
                    if (r == 0)
                            return x!=y ? x-y : x+y;
                    else
                            if (r > 0)
                            {
                                    r -= y+y+1;
                                    ++y;
                            }
                            else
                            {
                                    r += x+x+1;
                                    ++x;
                            }
    }
    template <class T, class T2>
    T2 prime_div_trivial (const T & n, T2 m)
    {
            // сначала проверяем тривиальные случаи
            if (n == 2 || n == 3)
                    return 1;
            if (n < 2)
                    return 0;
            if (even (n))
                    return 2;
            // генерируем простые от 3 до m
            T2 pi;
            const vector<T2> & primes = get_primes (m, pi);
            // делим на все простые
            for (std::vector<T2>::const_iterator iter=primes.begin(), end=primes.
    end();
                    iter!=end; ++iter)
            {
                    const T2 & div = *iter;
                    if (div * div > n)
                            break;
                    else
                            if (n % div == 0)
                                    return div;
            }
            if (n < m*m)
                    return 1;
            return 0;
    }
    template <class T, class T2>
    void factorize (const T & n, std::map<T,unsigned> & result, T2 unused)
    {
            if (n == 1)
                    ;
            else
                    // проверяем, не простое ли число
                    if (isprime (n))
                            ++result[n];
                    else
                            // если число достаточно маленькое, то его
    разлагаем простым перебором
                            if (n < 1000*1000)
                            {
                                    T div = prime_div_trivial (n, 1000);
                                    ++result[div];
                                    factorize (n / div, result, unused);
                            }
                            else
                            {
                                    // число большое, запускаем на нем
    алгоритмы факторизации
                                    T div;
                                    // сначала идут быстрые алгоритмы Полларда
                                    div = pollard_monte_carlo (n);
                                    if (div == 1)
                                            div = pollard_rho (n);
                                    if (div == 1)
                                            div = pollard_p_1 (n);
                                    if (div == 1)
                                            div = pollard_bent (n);
                                    // придётся запускать 100%-ый алгоритм Ферма
                                    if (div == 1)
                                            div = ferma (n, unused);
                                    // рекурсивно обрабатываем найденные множители
                                    factorize (div, result, unused);
                                    factorize (n / div, result, unused);
                            }
    }
}

C++ 해법

매칭됨/원본
template <class T>
T pollard_p_1 (T n)
{
        // параметры алгоритма, существенно влияют на производительность
и качество поиска
        const T b = 13;
        const T q[] = { 2, 3, 5, 7, 11, 13 };
        // несколько попыток алгоритма
        T a = 5 % n;
        for (int j=0; j<10; j++)
        {
                // ищем такое a, которое взаимно просто с n
                while (gcd (a, n) != 1)
                {
                        mulmod (a, a, n);
                        a += 3;
                        a %= n;
                }
                // вычисляем a^M
                for (size_t i = 0; i < sizeof q / sizeof q[0]; i++)
                {
                        T qq = q[i];
                        T e = (T) floor (log ((double)b) / log ((double)qq));
                        T aa = powmod (a, powmod (qq, e, n), n);
                        if (aa == 0)
                                continue;
                        // проверяем, не найден ли ответ
                        T g = gcd (aa-1, n);
                        if (1 < g && g < n)
                                return g;
                }
        }
        // если ничего не нашли
        return 1;
}
template <class T>
T pollard_rho (T n, unsigned iterations_count = 100000)
{
        T
                b0 = rand() % n,
                b1 = b0,
                g;
        mulmod (b1, b1, n);
        if (++b1 == n)
                b1 = 0;
        g = gcd (abs (b1 - b0), n);
        for (unsigned count=0; count<iterations_count && (g == 1 || g ==
n); count++)
        {
                mulmod (b0, b0, n);
                if (++b0 == n)
                        b0 = 0;
                mulmod (b1, b1, n);
                ++b1;
                mulmod (b1, b1, n);
                if (++b1 == n)
                        b1 = 0;
                g = gcd (abs (b1 - b0), n);
        }
        return g;
}
template <class T>
T pollard_bent (T n, unsigned iterations_count = 19)
{
        T
                b0 = rand() % n,
                b1 = (b0*b0 + 2) % n,
                a = b1;
        for (unsigned iteration=0, series_len=1;
iteration<iterations_count; iteration++, series_len*=2)
        {
                T g = gcd (b1-b0, n);
                for (unsigned len=0; len<series_len && (g==1 && g==n); len++)
                {
                        b1 = (b1*b1 + 2) % n;
                        g = gcd (abs(b1-b0), n);
                }
                b0 = a;
                a = b1;
                if (g != 1 && g != n)
                        return g;
        }
        return 1;
}
template <class T>
T pollard_monte_carlo (T n, unsigned m = 100)
{
        T b = rand() % (m-2) + 2;
        static std::vector<T> primes;
        static T m_max;
        if (primes.empty())
                primes.push_back (3);
        if (m_max < m)
        {
                m_max = m;
                for (T prime=5; prime<=m; ++++prime)
                {
                        bool is_prime = true;
                        for (std::vector<T>::const_iterator iter=primes.
begin(), end=primes.end();
                                iter!=end; ++iter)
                        {
                                T div = *iter;
                                if (div*div > prime)
                                        break;
                                if (prime % div == 0)
                                {
                                        is_prime = false;
                                        break;
                                }
                        }
                        if (is_prime)
                                primes.push_back (prime);
                }
        }
        T g = 1;
        for (size_t i=0; i<primes.size() && g==1; i++)
        {
                T cur = primes[i];
                while (cur <= n)
                        cur *= primes[i];
                cur /= primes[i];
                b = powmod (b, cur, n);
                g = gcd (abs(b-1), n);
                if (g == n)
                        g = 1;
        }
        return g;
}
template <class T, class T2>
T ferma (const T & n, T2 unused)
{
        T2
                x = sq_root (n),
                y = 0,
                r = x*x - y*y - n;
        for (;;)
                if (r == 0)
                        return x!=y ? x-y : x+y;
                else
                        if (r > 0)
                        {
                                r -= y+y+1;
                                ++y;
                        }
                        else
                        {
                                r += x+x+1;
                                ++x;
                        }
}
template <class T, class T2>
T2 prime_div_trivial (const T & n, T2 m)
{
        // сначала проверяем тривиальные случаи
        if (n == 2 || n == 3)
                return 1;
        if (n < 2)
                return 0;
        if (even (n))
                return 2;
        // генерируем простые от 3 до m
        T2 pi;
        const vector<T2> & primes = get_primes (m, pi);
        // делим на все простые
        for (std::vector<T2>::const_iterator iter=primes.begin(), end=primes.
end();
                iter!=end; ++iter)
        {
                const T2 & div = *iter;
                if (div * div > n)
                        break;
                else
                        if (n % div == 0)
                                return div;
        }
        if (n < m*m)
                return 1;
        return 0;
}
template <class T, class T2>
void factorize (const T & n, std::map<T,unsigned> & result, T2 unused)
{
        if (n == 1)
                ;
        else
                // проверяем, не простое ли число
                if (isprime (n))
                        ++result[n];
                else
                        // если число достаточно маленькое, то его
разлагаем простым перебором
                        if (n < 1000*1000)
                        {
                                T div = prime_div_trivial (n, 1000);
                                ++result[div];
                                factorize (n / div, result, unused);
                        }
                        else
                        {
                                // число большое, запускаем на нем
алгоритмы факторизации
                                T div;
                                // сначала идут быстрые алгоритмы Полларда
                                div = pollard_monte_carlo (n);
                                if (div == 1)
                                        div = pollard_rho (n);
                                if (div == 1)
                                        div = pollard_p_1 (n);
                                if (div == 1)
                                        div = pollard_bent (n);
                                // придётся запускать 100%-ый алгоритм Ферма
                                if (div == 1)
                                        div = ferma (n, unused);
                                // рекурсивно обрабатываем найденные множители
                                factorize (div, result, unused);
                                factorize (n / div, result, unused);
                        }
}

Java 해법

자동 초안, 제출 전 검토
import java.util.*;
import java.math.*;

public class AlgorithmDraft {
    // Auto-generated Java draft from the original e-maxx C/C++ listing. Review before production use.
    template <class T>
    T pollard_p_1 (T n)
    {
            // параметры алгоритма, существенно влияют на производительность
    и качество поиска
            const T b = 13;
            const T q[] = { 2, 3, 5, 7, 11, 13 };
            // несколько попыток алгоритма
            T a = 5 % n;
            for (int j=0; j<10; j++)
            {
                    // ищем такое a, которое взаимно просто с n
                    while (gcd (a, n) != 1)
                    {
                            mulmod (a, a, n);
                            a += 3;
                            a %= n;
                    }
                    // вычисляем a^M
                    for (size_t i = 0; i < sizeof q / sizeof q[0]; i++)
                    {
                            T qq = q[i];
                            T e = (T) floor (log ((double)b) / log ((double)qq));
                            T aa = powmod (a, powmod (qq, e, n), n);
                            if (aa == 0)
                                    continue;
                            // проверяем, не найден ли ответ
                            T g = gcd (aa-1, n);
                            if (1 < g && g < n)
                                    return g;
                    }
            }
            // если ничего не нашли
            return 1;
    }
    template <class T>
    T pollard_rho (T n, unsigned iterations_count = 100000)
    {
            T
                    b0 = rand() % n,
                    b1 = b0,
                    g;
            mulmod (b1, b1, n);
            if (++b1 == n)
                    b1 = 0;
            g = gcd (abs (b1 - b0), n);
            for (unsigned count=0; count<iterations_count && (g == 1 || g ==
    n); count++)
            {
                    mulmod (b0, b0, n);
                    if (++b0 == n)
                            b0 = 0;
                    mulmod (b1, b1, n);
                    ++b1;
                    mulmod (b1, b1, n);
                    if (++b1 == n)
                            b1 = 0;
                    g = gcd (abs (b1 - b0), n);
            }
            return g;
    }
    template <class T>
    T pollard_bent (T n, unsigned iterations_count = 19)
    {
            T
                    b0 = rand() % n,
                    b1 = (b0*b0 + 2) % n,
                    a = b1;
            for (unsigned iteration=0, series_len=1;
    iteration<iterations_count; iteration++, series_len*=2)
            {
                    T g = gcd (b1-b0, n);
                    for (unsigned len=0; len<series_len && (g==1 && g==n); len++)
                    {
                            b1 = (b1*b1 + 2) % n;
                            g = gcd (abs(b1-b0), n);
                    }
                    b0 = a;
                    a = b1;
                    if (g != 1 && g != n)
                            return g;
            }
            return 1;
    }
    template <class T>
    T pollard_monte_carlo (T n, unsigned m = 100)
    {
            T b = rand() % (m-2) + 2;
            static std::vector<T> primes;
            static T m_max;
            if (primes.empty())
                    primes.push_back (3);
            if (m_max < m)
            {
                    m_max = m;
                    for (T prime=5; prime<=m; ++++prime)
                    {
                            boolean is_prime = true;
                            for (std::vector<T>::const_iterator iter=primes.
    begin(), end=primes.end();
                                    iter!=end; ++iter)
                            {
                                    T div = *iter;
                                    if (div*div > prime)
                                            break;
                                    if (prime % div == 0)
                                    {
                                            is_prime = false;
                                            break;
                                    }
                            }
                            if (is_prime)
                                    primes.push_back (prime);
                    }
            }
            T g = 1;
            for (size_t i=0; i<primes.size() && g==1; i++)
            {
                    T cur = primes[i];
                    while (cur <= n)
                            cur *= primes[i];
                    cur /= primes[i];
                    b = powmod (b, cur, n);
                    g = gcd (abs(b-1), n);
                    if (g == n)
                            g = 1;
            }
            return g;
    }
    template <class T, class T2>
    T ferma (const T & n, T2 unused)
    {
            T2
                    x = sq_root (n),
                    y = 0,
                    r = x*x - y*y - n;
            for (;;)
                    if (r == 0)
                            return x!=y ? x-y : x+y;
                    else
                            if (r > 0)
                            {
                                    r -= y+y+1;
                                    ++y;
                            }
                            else
                            {
                                    r += x+x+1;
                                    ++x;
                            }
    }
    template <class T, class T2>
    T2 prime_div_trivial (const T & n, T2 m)
    {
            // сначала проверяем тривиальные случаи
            if (n == 2 || n == 3)
                    return 1;
            if (n < 2)
                    return 0;
            if (even (n))
                    return 2;
            // генерируем простые от 3 до m
            T2 pi;
            const vector<T2> & primes = get_primes (m, pi);
            // делим на все простые
            for (std::vector<T2>::const_iterator iter=primes.begin(), end=primes.
    end();
                    iter!=end; ++iter)
            {
                    const T2 & div = *iter;
                    if (div * div > n)
                            break;
                    else
                            if (n % div == 0)
                                    return div;
            }
            if (n < m*m)
                    return 1;
            return 0;
    }
    template <class T, class T2>
    void factorize (const T & n, std::map<T,unsigned> & result, T2 unused)
    {
            if (n == 1)
                    ;
            else
                    // проверяем, не простое ли число
                    if (isprime (n))
                            ++result[n];
                    else
                            // если число достаточно маленькое, то его
    разлагаем простым перебором
                            if (n < 1000*1000)
                            {
                                    T div = prime_div_trivial (n, 1000);
                                    ++result[div];
                                    factorize (n / div, result, unused);
                            }
                            else
                            {
                                    // число большое, запускаем на нем
    алгоритмы факторизации
                                    T div;
                                    // сначала идут быстрые алгоритмы Полларда
                                    div = pollard_monte_carlo (n);
                                    if (div == 1)
                                            div = pollard_rho (n);
                                    if (div == 1)
                                            div = pollard_p_1 (n);
                                    if (div == 1)
                                            div = pollard_bent (n);
                                    // придётся запускать 100%-ый алгоритм Ферма
                                    if (div == 1)
                                            div = ferma (n, unused);
                                    // рекурсивно обрабатываем найденные множители
                                    factorize (div, result, unused);
                                    factorize (n / div, result, unused);
                            }
    }
}

Материал разбит как 알고리즘ическая 문제: изучить постановку, понять асимптотику и реализовать 알고리즘 на выбранном языке.

Vacancies for this task

활성 채용 with overlapping task tags are 표시됨.

전체 채용
아직 활성 채용이 없습니다.