Наименьшее кратное заданного числа С цифрами только 0 и 1 - PullRequest
5 голосов
/ 21 января 2020

Вам дано целое число N. Вы должны найти наименьшее кратное N, которое состоит только из цифр 0 и 1. Поскольку это кратное значение может быть большим, верните его в виде строки.

Возвращаемая строка не должна содержать начальных нулей.

Например,

Для N = 55, 110 - наименьшее кратное число, состоящее из цифр 0 и 1. Для N = 2 ответом является 10.

Я видел несколько связанных проблем, но не смог найти проблему с моим кодом. Вот мой код, дающий TLE в некоторых случаях даже после использования map вместо set.

#define ll long long
int getMod(string s, int A)
{
    int res=0;
    for(int i=0;i<s.length();i++)
    {
        res=res*10+(s[i]-'0');
        res%=A;
    }
    return res;
}
string Solution::multiple(int A) {
    if(A<=1)
    return to_string(A);

    queue<string>q;
    q.push("1");
    set<int>st;
    string s="1";

    while(!q.empty())
    {
        s=q.front();
        q.pop();
        int mod=getMod(s,A);
        if(mod==0)
        {
            return s;
        }
        else if(st.find(mod)==st.end())
        {
            st.insert(mod);
            q.push(s+"0");
            q.push(s+"1");
        }
    }

}

Ответы [ 3 ]

7 голосов
/ 01 марта 2020

Вот реализация в Raku.

my $n = 55;
(1 .. Inf).map( *.base(2) ).first( * %% $n );

(1 .. Inf) - это список lazy от одного до бесконечности. «Какая звезда» * устанавливает замыкание и обозначает текущий элемент в map.

base - это метод типа Rakus Num, который возвращает строковое представление заданного числа в искомой базе, здесь двоичная строка.

first возвращает текущий элемент когда для него справедливо замыкание «любая звезда».

%% является оператором divisible by, оно неявно переводит свою левую сторону в Int.

О, и в довершение. Это просто до распараллелить это, так что ваш код может использовать несколько ядер процессора:

 (1 .. Inf).race( :batch(1000), :degree(4) ).map( *.base(2) ).first( * %% $n );
2 голосов
/ 21 января 2020

Как упомянуто в «математической» справке, результат связан с конгруэнцией степени 10 по модулю A.

Если

n = sum_i a[i] 10^i

, то

n modulo A = sum_i a[i] b[i]

Где a[i] равны 0 или 1, а b[i] = (10^i) modulo A

Тогда проблема состоит в том, чтобы найти последовательность минимум a[i], такую, что сумма равна 0 по модулю A.

С точки зрения графа, мы должны найти кратчайший путь к нулю по модулю A.

BFS, как правило, хорошо приспособлен для поиска такого пути. Проблема заключается в возможном экспоненциальном увеличении количества посещаемых узлов. Здесь мы должны были получить число узлов меньше A, отклоняя узлы, сумма которых (по модулю A) уже была получена (см. Вектор used в программе). Обратите внимание, что этот отказ необходим для того, чтобы получить минимальное количество в конце.

Вот программа на C ++. Решение довольно простое, оно должно быть легким для понимания даже теми, кто не знаком с C ++.

#include <iostream>
#include <string>
#include <vector>

struct node {
    int sum = 0;
    std::string s;
};

std::string multiple (int A) {
    std::vector<std::vector<node>> nodes (2);
    std::vector<bool> used (A, false);
    int range = 0;
    int ten = 10 % A;
    int pow_ten = 1;

    if (A == 0) return "0";
    if (A == 1) return "1";

    nodes[range].push_back (node{0, "0"});
    nodes[range].push_back (node{1, "1"});
    used[1] = true;

    while (1) {
        int range_new = (range + 1) % 2;
        nodes[range_new].resize(0);
        pow_ten = (pow_ten * ten) % A;

        for (node &x: nodes[range]) {
            node y = x;
            y.s = "0" + y.s;
            nodes[range_new].push_back(y);
            y = x;
            y.sum = (y.sum + pow_ten) % A;
            if (used[y.sum]) continue;
            used[y.sum] = true;
            y.s = "1" + y.s;
            if (y.sum == 0) return y.s;
            nodes[range_new].push_back(y);
        }
        range = range_new;
    }
}

int main() {
    std::cout << "input number: ";
    int n;
    std::cin >> n;
    std::cout << "Result = " << multiple(n) << "\n";
    return 0;
}

EDIT

В приведенной выше программе используется вид запоминание, чтобы ускорить процесс, но для больших входов память становится слишком большой. Как указано в комментарии, например, он не может обработать случай N = 60000007.

Я немного улучшил скорость и диапазон с помощью следующих модификаций:

  • Функция ( reduction) был создан для упрощения поиска, когда входное число делится на 2 или 5
  • Для запоминания узлов (массив nodes) теперь используется только один массив вместо двух
  • Используется разновидность промежуточной процедуры: на первом шаге функция mem_gen запоминает все соответствующие 01 последовательности до N_DIGIT_MEM (= 20) цифр. Затем основная процедура multiple2 генерирует действительные последовательности 01 «после 20 первых цифр», а затем в памяти ищет «дополнительную последовательность», так что объединение обоих является действительной последовательностью

С Эта новая программа N = 60000007 обеспечивает хороший результат (100101000001001010011110111, 27 цифр) примерно за 600 мс на моем P C.

РЕДАКТИРОВАТЬ 2

Вместо ограничения количества цифр для запоминания на первом шаге, я теперь использую порог для размера памяти, так как этот размер не зависит только от количества цифр, но и от номера ввода. Обратите внимание, что оптимальное значение этого порога будет зависеть от входного числа. Здесь я выбрал в качестве компромисса порог в 50к. С порогом 20к, для 60000007 я получаю хороший результат за 36 мс. Кроме того, с порогом 100k, худший случай 99999999 решается за 5 секунд.

Я провел различные тесты со значениями менее 10 ^ 9. Примерно во всех проверенных случаях результат предоставляется менее чем за 1 с. Однако я встретил угловой случай N = 99999999, для которого результат состоит из 72 последовательных «1». В этом конкретном случае программа занимает около 6,7 с. Для 60000007 хороший результат получается за 69мс.

Вот новая программа:

    #include <iostream>
    #include <string>
    #include <vector>
    #include <map>
    #include <unordered_map>
    #include <chrono>
    #include <cmath>
    #include <algorithm>

    std::string reverse (std::string s) {
        std::string res {s.rbegin(), s.rend()};
        return res;
    }

    struct node {
        int sum = 0;
        std::string s;
        node (int sum_ = 0, std::string s_ = ""): sum(sum_), s(s_) {};
    };

//  This function simplifies the search when the input number is divisible by 2 or 5
    node reduction (int &X, long long &pow_ten) {
        node init {0, ""};
        while (1) {
            int digit = X % 10;
            if (digit == 1 || digit == 3 || digit == 7 || digit == 9) break;
            switch (digit) {
                case(0):
                    X /= 10;
                    break;
                case(2):
                case(4):
                case(6):
                case(8):
                    X = (5*X)/10;
                    break;
                case(5):
                    X = (2*X)/10;
                    break;
            }
            init.s.push_back('0');
            pow_ten = (pow_ten * 10) % X;
        }       
        return init;
    }

const int N_DIGIT_MEM = 30;     // 20
const int threshold_size_mem = 50000;

//  This function memorizes all relevant 01 sequences up to N_DIGIT_MEM digits
bool gene_mem (int X, long long &pow_ten, int index_max, std::map<int, std::string> &mem, node &result) {

    std::vector<node> nodes;
    std::vector<bool> used (X, false);
    bool start = true;

    for (int index = 0; index < index_max; ++index){
        if (start) {
                node x = {int(pow_ten), "1"};
                nodes.push_back (x);
        } else {
            for (node &x: nodes) {
                x.s.push_back('0');
            }
            int n = nodes.size();

            for (int i = 0; i < n; ++i) {
                node y = nodes[i];
                y.sum = (y.sum + pow_ten) % X;
                y.s.back() = '1';
                if (used[y.sum]) continue;
                used[y.sum] = true;
                if (y.sum == 0) {
                    result = y;
                    return true;
                }
                nodes.push_back(y);
            }   
        }
        pow_ten = (10 * pow_ten) % X;
        start = false;
        int n_mem = nodes.size();
        if (n_mem > threshold_size_mem) {
            break;
        }
    }
    for (auto &x: nodes) {
        mem[x.sum] = x.s;
    }
    //std::cout << "size mem = " << mem.size() << "\n";
    return false;
}
//  This function generates valid 01 sequences "after the 20 first digits" and then in the memory 
//  looks for a "complementary sequence" such that the concatenation of both is a valid sequence
    std::string multiple2 (int A) {
        std::vector<node> nodes;
        std::map<int, std::string> mem;
        int ten = 10 % A;
        long long pow_ten = 1;
        int digit;

        if (A == 0) return "0";
        int X = A;
        node init = reduction (X, pow_ten);

        if (X != A) ten = ten % X;

        if (X == 1) {
            init.s.push_back('1');
            return reverse(init.s);
        }
        std::vector<bool> used (X, false);
        node result;
        int index_max = N_DIGIT_MEM;
        if (gene_mem (X, pow_ten, index_max, mem, result)) {
            return reverse(init.s + result.s);
        }   

        node init2 {0, ""};
        nodes.push_back(init2);

        while (1) {
            for (node &x: nodes) {
                x.s.push_back('0');
            }
            int n = nodes.size();
            for (int i = 0; i < n; ++i) {
                node y = nodes[i];
                y.sum = (y.sum + pow_ten) % X;
                if (used[y.sum]) continue;
                used[y.sum] = true;
                y.s.back() = '1';
                if (y.sum != 0) {
                    int target = X - y.sum;
                    auto search = mem.find(target);
                    if (search != mem.end()) {
                        //std::cout << "mem size 2nd step = " << nodes.size() << "\n";
                        return reverse(init.s + search->second + y.s);
                    }
                }
                nodes.push_back(y);
            }
            pow_ten = (pow_ten * ten) % X;
        }
    }

    int main() {
        std::cout << "input number: ";
        int n;
        std::cin >> n;
        std::string res;

        auto t1 = std::chrono::high_resolution_clock::now();
        res = multiple2(n),
        std::cout << "Result = " << res << "  ndigit = " << res.size() << std::endl;
        auto t2 = std::chrono::high_resolution_clock::now();
        auto duration2 = std::chrono::duration_cast<std::chrono::microseconds>( t2 - t1 ).count();
        std::cout << "time = " << duration2/1000 << " ms" << std::endl;

        return 0;
    }
0 голосов
/ 24 января 2020

Для людей, более знакомых с Python, вот преобразованная версия кода @ Damien. Важное понимание Дэмиена заключается в том, чтобы сильно сократить дерево поиска, используя тот факт, что каждую частичную сумму нужно исследовать только один раз, а именно при первом обнаружении.

Проблема также описана в Mathpuzzle , но там они в основном фиксируют необходимость существования решения. Также есть код, упомянутый в онлайн-энциклопедии целочисленных последовательностей . версия мудреца кажется несколько похожей.

Я внес несколько изменений:

  • Начало с пустого списка помогает правильно решить A=1 при упрощении код. Умножение на 10 перемещается в конец l oop. Делать то же самое для 0 кажется трудным, поскольку log10(0) - это minus infinity.
  • Вместо чередования nodes[range] и nodes[new_range] используются два разных списка.
  • Поскольку Python поддерживает целые числа произвольной точности, частичные результаты могут быть сохранены как десятичные или двоичные числа, а не как строки. Это еще не сделано в коде ниже.
from collections import namedtuple

node = namedtuple('node', 'sum str')

def find_multiple_ones_zeros(A):
    nodes = [node(0, "")]
    used = set()
    pow_ten = 1
    while True:
        new_nodes = []
        for x in nodes:
            y = node(x.sum, "0" + x.str)
            new_nodes.append(y)
            next_sum = (x.sum + pow_ten) % A
            y = node((x.sum + pow_ten) % A, x.str)
            if next_sum in used:
                continue
            used.add(next_sum)
            y = node(next_sum, "1" + x.str)
            if next_sum == 0:
                return y.str
            new_nodes.append(y)
        pow_ten = (pow_ten * 10) % A
        nodes = new_nodes
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...