Алгоритм составления чисел из спичек - PullRequest
7 голосов
/ 21 июля 2011

Я сделал программу для решения этой проблемы из ACM.

Спички являются идеальными инструментами для представления чисел.Обычный способ представления десятичных десятичных цифр с помощью спичек заключается в следующем:

Это идентично отображению чисел на обычном будильнике.С заданным количеством спичек вы можете генерировать широкий диапазон чисел.Нам интересно, какие самые маленькие и самые большие числа могут быть созданы с использованием всех ваших спичек.

Ввод

В первой строке одно положительное число: количество тестовых случаев, самое большее 100.После этого для каждого теста:

Одна строка с целым числом n (2 ≤ n ≤ 100): количество спичек у вас есть.Выходные данные

Для каждого теста:

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

4 3 6 7 15 Пример вывода

7 7 6 111 8 711 108 7111111

Проблема в том, что его решение слишком медленное для100 спичек.Дерево поиска слишком велико, чтобы его можно было обработать.

Вот результаты за первые 10:

2: 1 1

3: 7 7

4: 4 11

5: 2 71

6: 6 111

7: 8 711

8: 10 1111

9: 18 7111

10: 22 11111

Шаблон для максимумов прост, но я не вижу ярлыка для минимумов.Может кто-нибудь предложить лучший способ решить эту проблему?Вот код, который я использовал:

    #include <iostream>
    #include <string>
    using namespace std;

    #define MAX 20 //should be 100

    //match[i] contains number of matches needed to form i
    int match[] = {6, 2, 5, 5, 4, 5, 6, 3, 7, 6};
    string mi[MAX+1], ma[MAX+1];
    char curr[MAX+1] = "";

    //compare numbers saved as strings
    int mycmp(string s1, string s2)
    {
        int n = (int)s1.length();
        int m = (int)s2.length();
        if (n != m)
            return n - m;
        else
            return s1.compare(s2);
    }

    //i is the current digit, used are the number of matchsticks so far
    void fill(int i, int used)
    {
        //check for smaller and bigger values
        if (mycmp(curr, mi[used]) < 0) mi[used] = curr;
        if (mycmp(curr, ma[used]) > 0) ma[used] = curr;

        //recurse further, don't start numbers with a zero
        for (int a = i ? '0' : '1'; a <= '9'; a++) {
            int next = used + match[a-'0'];
            if (next <= MAX) {
                curr[i] = a;
                curr[i+1] = '\0';
                fill(i + 1, next);
            }
        }
    }

    int main()
    {
        //initialise 
        for (int i = 0; i <= MAX; i++) {
            mi[i] = string(MAX, '9');
            ma[i] = "0";
        }

        //precalculate the values
        fill(0, 0);

        int n;
        cin >> n;

        //print those that were asked
        while (n--) {
            int num;
            cin >> num;
            cout << mi[num] << " " << ma[num] << endl;
        }

        return 0;
    }

РЕДАКТИРОВАТЬ : я закончил с использованием решения динамического программирования.Я пробовал это с dp раньше, но я возился с двумерным массивом состояний.Решения здесь намного лучше.Спасибо!

Ответы [ 6 ]

4 голосов
/ 21 июля 2011

Вы можете использовать решение для динамического программирования.

Предположим, что у вас есть n совпадений, и вы знаете, как решить задачу (минимальное число) для всего набора n-k совпадений, где k занимаетвсе значения, соответствующие количеству совпадений, которые использует каждое число (2 для 1, 5 для 3 и т. д.)

Решение затем получается рекурсивно.Предположим, что вы заканчиваете свой номер единицей (в наименее значимой позиции), тогда наилучшее решение получается, написав (best solution with n-2 matches) 1.Предположим, что вы заканчиваете с двумя, лучшее решение - (best solution with n-5 matches) 2.И так далее ;в конце концов вы можете сравнить эти десять чисел и выбрать лучшее.

Итак, теперь все, что вам нужно сделать, - это найти лучшее решение для всех n рекурсивно меньших, чем ваш ввод.

РЕДАКТИРОВАТЬ: Если вы реализуете этот алгоритм простым способом, вы получите экспоненциальную сложность.Хитрость в том, чтобы заметить, что если ваша основная функция MinimumGivenNMatches, принимает только один параметр, n.Следовательно, вы в конечном итоге будете называть его одинаковыми значениями огромное количество раз.

Чтобы сделать сложность линейной, вам просто нужно запомнить (т.е. запомнить) решение для каждого n, используявспомогательный массив.

2 голосов
/ 21 июля 2011

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

Основная идея состоит в том, чтобы иметь массив min, который отслеживает минимальное число, которое может быть сделано с использованием точно n спичек.Итак

min[0] = ""
min[1] = infinity
min[2] = "1"
min[3] = min("1+"min[3-2],"7")
min[4] = min("1"+min[4-2],"7"+min[4-3])
etc
2 голосов
/ 21 июля 2011

Чтобы найти результат:

  • сначала найдите минимальное количество цифр для наименьшего числа
  • , затем перейдите от самой старшей к младшей цифре.

Каждая цифра должна быть выбрана так, чтобы существовало решение для оставшихся цифр.Каждая цифра требует от 2 до 7 совпадений.Таким образом, вы должны выбрать наименьшую N-ю «верхнюю» цифру, которая оставляет количество оставшихся совпадений от 2 * (N-1) до 7 * (N-1).

Не забывайте, что 0 нужно исключитьот поиска самой значимой цифры результата.

В качестве обозначения, одна из причин, по которой этот алгоритм работает, заключается в том, что существует по крайней мере одна соответствующая цифра для каждого значения (совпадений) между 2 и7.

РЕДАКТИРОВАТЬ: пример для 10 совпадений 10 совпадений -> 2 цифры
Допустимое количество совпадений для старшей цифры = от 3 до 7.
Наименьшая цифра, которая требуетот 3 до 7 совпадений -> 2 (что требует 5 совпадений), исключая 0.
Выбранная первая цифра = 2

5 оставшихся совпадений ->
Допустимое количество совпадений для второго (ив этом случае последняя) цифра = ровно 5
наименьшая цифра, которая требует 5 совпадений -> 2
выбранная вторая цифра = 2

результат = 22.

РЕДАКТИРОВАТЬ код для этой проблемы

#include <iostream>
#include <vector>

std::vector<int> nbMatchesForDigit;

long long minNumberForMatches(unsigned int nbMatches)
{
    int nbMaxMatchesForOneDigit = 7;
    int nbMinMatchesForOneDigit = 2;
    int remainingMatches = nbMatches;
    int nbDigits = 1 + nbMatches / nbMaxMatchesForOneDigit; 
    long long result = 0;
    for (int idDigit = 0 ; idDigit < nbDigits ; ++idDigit )
    {
        int minMatchesToUse = std::max(nbMinMatchesForOneDigit, remainingMatches - nbMaxMatchesForOneDigit * (nbDigits - 1 - idDigit));
        int maxMatchesToUse = std::min(nbMaxMatchesForOneDigit, remainingMatches - nbMinMatchesForOneDigit * (nbDigits - 1 - idDigit));
        for (int digit = idDigit > 0 ? 0 : 1 ; digit <= 9 ; ++digit )
        {
            if( nbMatchesForDigit[digit] >= minMatchesToUse && 
                nbMatchesForDigit[digit] <= maxMatchesToUse )
            {
                result = result * 10 + digit;
                remainingMatches -= nbMatchesForDigit[digit];
                break;
            }
        }
    }
    return result;
}

int main()
{
    nbMatchesForDigit.push_back(6);
    nbMatchesForDigit.push_back(2);
    nbMatchesForDigit.push_back(5);
    nbMatchesForDigit.push_back(5);
    nbMatchesForDigit.push_back(4);
    nbMatchesForDigit.push_back(5);
    nbMatchesForDigit.push_back(6);
    nbMatchesForDigit.push_back(3);
    nbMatchesForDigit.push_back(7);
    nbMatchesForDigit.push_back(6);

    for( int i = 2 ; i <= 100 ; ++i )
    {
        std::cout << i << " " << minNumberForMatches(i) << std::endl;
    }
}
1 голос
/ 21 июля 2011

Для минимумов учтите, что, поскольку начальные нули не разрешены, вы хотите минимизировать количество цифр. Минимальное количество цифр: ceil(n/7).

Тогда довольно просто вычислить минимальное количество спичек, которые ДОЛЖНЫ использоваться в первой цифре, из этого вы получите наименьшее возможное значение первой цифры.

0 голосов
/ 31 октября 2017
#include <iostream>
#include <vector>
#include <string>
#include <cstdlib>
#include <map>
using namespace std;

int main()
{
//  freopen("22.txt", "r", stdin);
    vector<char> count(10);
    map<int, char> Min_1;
    Min_1[0] = '8';
    Min_1[2] = '1';
    Min_1[3] = '7';
    Min_1[4] = '4';
    Min_1[5] = '2';
    count[0] = '6';
    count[1] = '2';
    count[2] = '5';
    count[3] = '5';
    count[4] = '4';
    count[5] = '5';
    count[6] = '6';
    count[7] = '3';
    count[8] = '7';
    count[9] = '6';
    int N = 99, P = 2;
    cin >> N;
    while(N --)
    {
        cin >> P;
        vector<char> min, max;
        int a = P, b = P;
        int total = (a + 6) / 7;
        int left = a % 7;
        bool first = true;
        char lastInsert = 0;
        while(a != 0)
        {
            if(total == 1)
            {
                if(left != 6)
                {
                    min.push_back(Min_1[left]);
                }else if(left == 6)
                {
                    if(!first)
                        min.push_back('0');
                    else
                        min.push_back('6');
                }
                break;
            }
            if(left == 0)
            {
                min.insert(min.end(), total, '8');
                break;
            }else{
                if(first && left <= 2)
                {
                    min.push_back('1');
                    lastInsert = 1;
                }else if(first && left < 6)
                {
                    min.push_back('2');
                    lastInsert = 2;
                }else if(first && left == 6)
                {
                    min.push_back('6');
                    lastInsert = 6;
                }else if(!first)
                {
                    min.push_back('0');
                    lastInsert = 0;
                } 
            }
            int temp = count[lastInsert] - '0';
            a -= temp;
            left = a % 7;
            total = (a + 6) / 7;
            first = false;
        }

        if(b % 2 == 1)
        {
            max.push_back('7');
            max.insert(max.end(), (b - 3) / 2, '1');
        }else{
            max.insert(max.end(), b / 2, '1');
        }
        string res_min = string(min.begin(), min.end());
        string res_max = string(max.begin(), max.end());
        cout << res_min << " " << res_max << endl;
        P ++;
    }
    return 0;
}

Это мой ответ, желаю, чтобы он был полезен

0 голосов
/ 03 января 2017

Я могу решить проблему с помощью O(d), где d - количество цифр. Идея в том, чтобы сначала мы вычислили минимальное количество цифр для желаемого минимального числа. Это можно рассчитать как int nofDigits = n/7+ ((0 == n%7) ? 0 : 1), где n - количество спичек. Теперь создайте массив nofDigits. Теперь мы начинаем заполнять максимально возможные спички (7) от наименее значимой цифры до одной цифры перед максимально значимой цифрой (MSD) и в конце присваиваем все оставшиеся спички самой значимой цифре (MSD). Теперь есть 3 возможности улучшения в зависимости от количества спичек для MSD:

1-е, если число спичек для MSD равно 1, то мы можем сделать его 2, заимствуя одну спичку из соседней цифры. Это сделает количество спичек для соседней цифры 6, что эквивалентно 0

2d, если количество спичек для MSD равно 4, то также как и в предыдущем случае, мы можем увеличить количество спичек для MSD до 5, что эквивалентно 2

3-й, если количество спичек для MSD равно 3, то мы должны увидеть, если общее число цифр больше 2, то мы можем уменьшить 1 с двух соседних цифр MSD, иначе мы уменьшим одну соседнюю цифру дважды и увеличим количество спичек для MSD на 2

В завершение путем обхода массива и замены числа спичек соответствующей цифрой.

Полная программа:

void minNumWithNMatches_no_dp (int n) {
    if (n < 2) return ;

    int digits[] = {-1, -1, 1, 7, 4, 2, 0, 8};

    int nofDigits = n/7+ ((0 == n%7) ? 0 : 1);

    int *matchesArr = new int [nofDigits];

    int tmp = n;
    for (int i = 0; i < nofDigits; ++i) {
        matchesArr[i] = tmp/7 ? 7 : tmp%7;
        tmp -= matchesArr[i];
    }

    if (nofDigits > 1)
    {
        switch (matchesArr[nofDigits - 1]) {
        case 1:
        case 4:
            {
                --matchesArr[nofDigits - 2];
                ++matchesArr[nofDigits - 1];
                break;
            }
        case 3:
            {
                if (nofDigits > 2) {
                    --matchesArr[nofDigits - 2];
                    --matchesArr[nofDigits - 3];
                } else {
                    matchesArr[nofDigits - 2] -= 2;
                }
                matchesArr[nofDigits - 1] += 2;
                break;
            }
        case 2:
        case 5:
        case 6:
        case 7:
        default:
            {
                ;
            }
        }
    }
    for (int i = nofDigits - 1; i >= 0; --i) {
        int digit = digits[matchesArr[i]];
        if ((i == nofDigits - 1) && (digit == 0)) digit = 6;
        std::cout<< digit;
    }
}
...