Алгоритм для решения задач на динамическое программирование

Олдфаг
Статус
Оффлайн
Регистрация
18 Фев 2019
Сообщения
2,826
Реакции[?]
1,853
Поинты[?]
24K
Решил поделиться с вами небольшим алгоритмом для решения типовых задач из ЕГЭ по информатике, основанных на принципе динамического программирования. Об этом интересном олимпиадном направлении хорошо рассказывали на
Пожалуйста, авторизуйтесь для просмотра ссылки.
, можете почитать. Я же решил написать небольшую программу, позволяющую решать задания об "Исполнителях Калькулятор, РазДваТри и т.д.", примеров подобных "непосильных" заданий очень много, вот ссылка на небольшой сборник таких -
Пожалуйста, авторизуйтесь для просмотра ссылки.
.

В общем и целом, такие задачи обычно состоят из задачи найти количество программ, которые с помощью выполнения заданных операций в любом порядке смогут превратить число X в Y. Иногда уточняется обязательное использование или же обход некоторых чисел. Предлагаю вам немного разобраться с одной из них:
Исполнитель Калькулятор преобразует число на экране. У исполнителя есть две команды, которым присвоены номера:
1. Прибавить 1
2. Умножить на 2
Программа для исполнителя Калькулятор – это последовательность команд. Сколько существует программ, для которых при исходном числе 1 результатом является число 21, при этом траектория вычислений содержит число 10 и не содержит число 17?

Решение.
Нужно найти количество программ, которые из 1 получают 10, количество программ, которые из 10 получают 21, но не проходит через 17 и перемножить найденные значения. Сначала найдём количество программ, получающих 10 из 1.

Обозначим R(n) — количество программ, которые преобразуют число 2 в число n.
Верны следующие соотношения:
1. Если n не делится на 2, то тогда R(n) = R(n - 1), так как существует единственный способ получения n из n - 1 — прибавление единицы.
2. Пусть n делится на 2.

Если n > 1, то R(n) = R(n / 2) + R(n - 1).
Если n = 1, то R(n) = 1 (два способа: прибавление единицы и удвоение).

Теперь можно постепенно вычислить все значения:
R(2) = R(1) + R(1) = 1 + 1 = 2 = R(3)
R(4) = R(2) + R(3) = 2 + 2 = 4 = R(5),
R(6) = R(3) + R(5) = 2 + 4 = 6 = R(7),
R(8) = R(4) + R(7) = 4 + 6 = 10 = R(9),
R(10) = R(5) + R(9) = 4 + 10 = 14

Программ, получающих из числа 10 число 21, и не содержащих 17 всего одна: 21.
Тем самым, находим ответ: 14 · 1 = 14.
Ответ: 14.
Как видите, алгоритм решения не включает ничего сложного, он основывается на, надеюсь, известных вам принципах динамики. Интересным решением, как по мне, будет создание небольшой программы, которая способна вычислять ответ в подобных задачах без какие-либо сторонних расчетов. Основные значения, принимаемые на вход:
  • Используемые операции сложения и умножения (с вычитанием и делением дела обстоят ничуть не хуже, вы с легкостью сможете добавить их в свою программу при необходимости)
  • Начальное число
  • Конечное число
  • Обязательно используемые числа
  • "Запрещенные" числа
Вот, собственно, и все. Сам код я старался писать в максимально олимпиадном стиле - без использования сторонних алгоритов и с предусмотрением всех подводных камней, которые могут возникнуть в процессе решения. Также для удоства была использована сортировка входных массивов.

Исходный код:
C++:
#include <iostream>

using namespace std;

void Sort(int* mas, int size) {
    int i = 0;
    int j = size - 1;

    int mid = mas[size / 2];

    do {
        while (mas[i] < mid)
            i++;
        while (mas[j] > mid)
            j--;
        if (i <= j) {
            int tmp = mas[i];
            mas[i] = mas[j];
            mas[j] = tmp;

            i++;
            j--;
        }
    } while (i <= j);

    if (j > 0)
        Sort(mas, j + 1);
    if (i < size)
       Sort(&mas[i], size - i);
}

int main() {
    setlocale(LC_ALL, "ru");

    while (true) {

        system("cls");

        int N, plus[100], multiply[100], k1 = 0, k2 = 0;
        cout << "Введите количество возможных операций: ";
        cin >> N;
        cout << "Введите операции сложения и умножения: ";
        for (int i = 0; i < N; i++) {
            char c;
            int num;
            cin >> c >> num;
            if (c == '+')
                plus[k1++] = num;
            else if (c == '*')
                multiply[k2++] = num;
        }

        Sort(plus, k1);
        Sort(multiply, k2);

        int X, Y;
        cout << endl << "Введите X: ";
        cin >> X;
        cout << "Введите Y: ";
        cin >> Y;

        cout << endl << "Обязательные числа при вычислении (введите 0, если их нет): ";

        int use[100] = {}, notUse[100] = {}, _k1 = 1, _k2 = 0;
        use[0] = X;
        char c = cin.get();
        while (c = cin.get(), c != '\n') {
            if ((c == '0' || c == '\n') && _k1 == 1 && !use[_k1] && cin.get() == '\n') {
                _k1 = 0;
                break;
            }
            if (c != ' ')
                use[_k1] = use[_k1] * 10 + (c - '0');
            else {
                if (use[_k1] != use[_k1 - 1])
                    _k1++;
                else
                    use[_k1] = 0;
            }
        }
        if (use[_k1] != Y)
            use[++_k1] = Y;

        int mas[10000] = {}, count = 0;
        mas[0] = 1;

        cout << "Запрещенные числа (введите 0, если их нет): ";
        while (c = cin.get(), c != '\n') {
            if ((c == '0' || c == '\n') && !_k2 && !notUse[_k2] && cin.get() == '\n')
                break;
            if (c != ' ')
                notUse[_k2] = notUse[_k2] * 10 + (c - '0');
            else
                mas[notUse[_k2++] - X] = -1;
        }
        mas[notUse[_k2++] - X] = -1;

        Sort(use, _k1);
        Sort(notUse, _k2);

        for (int i = 0; i < _k1; i++) {
            for (int j = 0; j < _k2; j++)
                if (use[i] == notUse[j]) {
                    cout << endl << "Невозможно решить задачу!" << endl;
                    goto end;
                }
        }

        for (int i = 0; i < _k1; i++) {
            if (X == Y) {
                cout << "Ответ: 1 программа";
                break;
            }

            for (int j = use[i] + 1; j <= use[i + 1]; j++) {
                if (mas[j - X] == -1)
                    continue;
                for (int _p = 0; _p < k1; _p++) {
                    if (j - plus[_p] < use[i])
                        break;
                    else if (mas[j - plus[_p] - X] != -1)
                        mas[j - X] += mas[j - plus[_p] - X];
                }
                for (int _m = 0; _m < k2; _m++) {
                    if (!(j % multiply[_m])) {
                        if (j / multiply[_m] < use[i])
                            break;
                        else if (mas[j / multiply[_m] - X] != -1)
                            mas[j - X] += mas[j / multiply[_m] - X];
                    }
                }
            }

            if (mas[use[i + 1] - X] <= 0) {
                cout << endl << "Невозможно решить задачу!" << endl;
                break;
            }

            count = count <= 0 ? mas[use[i + 1] - X] : count * mas[use[i + 1] - X];
            mas[use[i + 1] - X] = 1;

            if (i == _k1 - 1) {
                cout << endl << "Ответ: " << count << endl;
                break;
            }
        }

    end:
        system("pause");
    }
    exit(0);
}
Пример работы на очередном задании из ЕГЭ:
1611594895698.png

На этом, думаю, стоит закончить. Если вы хотите увидеть продолжение с решением задач по динамике на двумерных массивах или графах - обязательно отпишите в тему, мне важно знать, что интересно аудитории :)
 
Участник
Статус
Оффлайн
Регистрация
3 Ноя 2020
Сообщения
874
Реакции[?]
181
Поинты[?]
0
Решил поделиться с вами небольшим алгоритмом для решения типовых задач из ЕГЭ по информатике, основанных на принципе динамического программирования. Об этом интересном олимпиадном направлении хорошо рассказывали на
Пожалуйста, авторизуйтесь для просмотра ссылки.
, можете почитать. Я же решил написать небольшую программу, позволяющую решать задания об "Исполнителях Калькулятор, РазДваТри и т.д.", примеров подобных "непосильных" заданий очень много, вот ссылка на небольшой сборник таких -
Пожалуйста, авторизуйтесь для просмотра ссылки.
.

В общем и целом, такие задачи обычно состоят из задачи найти количество программ, которые с помощью выполнения заданных операций в любом порядке смогут превратить число X в Y. Иногда уточняется обязательное использование или же обход некоторых чисел. Предлагаю вам немного разобраться с одной из них:
Исполнитель Калькулятор преобразует число на экране. У исполнителя есть две команды, которым присвоены номера:
1. Прибавить 1
2. Умножить на 2
Программа для исполнителя Калькулятор – это последовательность команд. Сколько существует программ, для которых при исходном числе 1 результатом является число 21, при этом траектория вычислений содержит число 10 и не содержит число 17?

Решение.
Нужно найти количество программ, которые из 1 получают 10, количество программ, которые из 10 получают 21, но не проходит через 17 и перемножить найденные значения. Сначала найдём количество программ, получающих 10 из 1.

Обозначим R(n) — количество программ, которые преобразуют число 2 в число n.
Верны следующие соотношения:
1. Если n не делится на 2, то тогда R(n) = R(n - 1), так как существует единственный способ получения n из n - 1 — прибавление единицы.
2. Пусть n делится на 2.

Если n > 1, то R(n) = R(n / 2) + R(n - 1).
Если n = 1, то R(n) = 1 (два способа: прибавление единицы и удвоение).

Теперь можно постепенно вычислить все значения:
R(2) = R(1) + R(1) = 1 + 1 = 2 = R(3)
R(4) = R(2) + R(3) = 2 + 2 = 4 = R(5),
R(6) = R(3) + R(5) = 2 + 4 = 6 = R(7),
R(8) = R(4) + R(7) = 4 + 6 = 10 = R(9),
R(10) = R(5) + R(9) = 4 + 10 = 14

Программ, получающих из числа 10 число 21, и не содержащих 17 всего одна: 21.
Тем самым, находим ответ: 14 · 1 = 14.
Ответ: 14.
Как видите, алгоритм решения не включает ничего сложного, он основывается на, надеюсь, известных вам принципах динамики. Интересным решением, как по мне, будет создание небольшой программы, которая способна вычислять ответ в подобных задачах без какие-либо сторонних расчетов. Основные значения, принимаемые на вход:
  • Используемые операции сложения и умножения (с вычитанием и делением дела обстоят ничуть не хуже, вы с легкостью сможете добавить их в свою программу при необходимости)
  • Начальное число
  • Конечное число
  • Обязательно используемые числа
  • "Запрещенные" числа
Вот, собственно, и все. Сам код я старался писать в максимально олимпиадном стиле - без использования сторонних алгоритов и с предусмотрением всех подводных камней, которые могут возникнуть в процессе решения. Также для удоства была использована сортировка входных массивов.

Исходный код:
C++:
#include <iostream>

using namespace std;

void Sort(int* mas, int size) {
    int i = 0;
    int j = size - 1;

    int mid = mas[size / 2];

    do {
        while (mas[i] < mid)
            i++;
        while (mas[j] > mid)
            j--;
        if (i <= j) {
            int tmp = mas[i];
            mas[i] = mas[j];
            mas[j] = tmp;

            i++;
            j--;
        }
    } while (i <= j);

    if (j > 0)
        Sort(mas, j + 1);
    if (i < size)
       Sort(&mas[i], size - i);
}

int main() {
    setlocale(LC_ALL, "ru");

    while (true) {

        system("cls");

        int N, plus[100], multiply[100], k1 = 0, k2 = 0;
        cout << "Введите количество возможных операций: ";
        cin >> N;
        cout << "Введите операции сложения и умножения: ";
        for (int i = 0; i < N; i++) {
            char c;
            int num;
            cin >> c >> num;
            if (c == '+')
                plus[k1++] = num;
            else if (c == '*')
                multiply[k2++] = num;
        }

        Sort(plus, k1);
        Sort(multiply, k2);

        int X, Y;
        cout << endl << "Введите X: ";
        cin >> X;
        cout << "Введите Y: ";
        cin >> Y;

        cout << endl << "Обязательные числа при вычислении (введите 0, если их нет): ";

        int use[100] = {}, notUse[100] = {}, _k1 = 1, _k2 = 0;
        use[0] = X;
        char c = cin.get();
        while (c = cin.get(), c != '\n') {
            if ((c == '0' || c == '\n') && _k1 == 1 && !use[_k1] && cin.get() == '\n') {
                _k1 = 0;
                break;
            }
            if (c != ' ')
                use[_k1] = use[_k1] * 10 + (c - '0');
            else {
                if (use[_k1] != use[_k1 - 1])
                    _k1++;
                else
                    use[_k1] = 0;
            }
        }
        if (use[_k1] != Y)
            use[++_k1] = Y;

        int mas[10000] = {}, count = 0;
        mas[0] = 1;

        cout << "Запрещенные числа (введите 0, если их нет): ";
        while (c = cin.get(), c != '\n') {
            if ((c == '0' || c == '\n') && !_k2 && !notUse[_k2] && cin.get() == '\n')
                break;
            if (c != ' ')
                notUse[_k2] = notUse[_k2] * 10 + (c - '0');
            else
                mas[notUse[_k2++] - X] = -1;
        }
        mas[notUse[_k2++] - X] = -1;

        Sort(use, _k1);
        Sort(notUse, _k2);

        for (int i = 0; i < _k1; i++) {
            for (int j = 0; j < _k2; j++)
                if (use[i] == notUse[j]) {
                    cout << endl << "Невозможно решить задачу!" << endl;
                    goto end;
                }
        }

        for (int i = 0; i < _k1; i++) {
            if (X == Y) {
                cout << "Ответ: 1 программа";
                break;
            }

            for (int j = use[i] + 1; j <= use[i + 1]; j++) {
                if (mas[j - X] == -1)
                    continue;
                for (int _p = 0; _p < k1; _p++) {
                    if (j - plus[_p] < use[i])
                        break;
                    else if (mas[j - plus[_p] - X] != -1)
                        mas[j - X] += mas[j - plus[_p] - X];
                }
                for (int _m = 0; _m < k2; _m++) {
                    if (!(j % multiply[_m])) {
                        if (j / multiply[_m] < use[i])
                            break;
                        else if (mas[j / multiply[_m] - X] != -1)
                            mas[j - X] += mas[j / multiply[_m] - X];
                    }
                }
            }

            if (mas[use[i + 1] - X] <= 0) {
                cout << endl << "Невозможно решить задачу!" << endl;
                break;
            }

            count = count <= 0 ? mas[use[i + 1] - X] : count * mas[use[i + 1] - X];
            mas[use[i + 1] - X] = 1;

            if (i == _k1 - 1) {
                cout << endl << "Ответ: " << count << endl;
                break;
            }
        }

    end:
        system("pause");
    }
    exit(0);
}
Пример работы на очередном задании из ЕГЭ:

На этом, думаю, стоит закончить. Если вы хотите увидеть продолжение с решением задач по динамике на двумерных массивах или графах - обязательно отпишите в тему, мне важно знать, что интересно аудитории :)
Годненько как всегда. Жаль что я в техе блять на последнем курсе :))
 
Я не гей! , Я Crpto Cl1pp3r
Забаненный
Статус
Оффлайн
Регистрация
20 Окт 2020
Сообщения
89
Реакции[?]
32
Поинты[?]
0
Обратите внимание, пользователь заблокирован на форуме. Не рекомендуется проводить сделки.
Чеееел , ты просто охуенен , спасибо огромное
 
Сверху Снизу