Не могу решить задачу (рекурсия)

Рейтинг: -3Ответов: 1Опубликовано: 14.07.2023

Есть задача:

Время: 1 сек. Память: 16 Мб В волшебной стране используются монетки достоинством A1, A2,..., AM. волшебный человечек пришел в магазин и обнаружил, что у него есть ровно по две монетки каждого достоинства. Ему нужно заплатить сумму N. Напишите программу, определяющую, сможет ли он расплатиться без сдачи.

Ввод:записано сначала число N (1 ≤ N ≤ 10^9), затем - число M (1 ≤ M ≤ 15) и далее M попарно различных чисел A1, A2,..., AM (1 ≤ Ai ≤ 10^9).

Вывод: выведите количество монет, которое придется отдать волшебному человечку, если он сможет заплатить указанную сумму без сдачи. Если решений несколько, выведите вариант, в котором волшебный человек отдаст наименьшее возможное количество монет. Если без сдачи не обойтись, то выведите одно число 0. Если же у волшебного человечка не хватит денег, чтобы заплатить указанную сумму, выведите одно число -1 (минус один).

Решал рекурсией на с++ но выдаёт ошибку(не правильный вывод) в одном из тестов я уже потерял надежду понять почему.(тесты на сайте посмотреть нельзя теперь ломаю голову) Вот код:

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
vector<pair<int,short>>v;
int rex(int n,int i){
    if(n==0){cout<<i;return 1;}
    if(n<0){return -1;}
    for(int x=v.size()-1;x>=0;--x){
        if(v[x].second!=0){
            v[x].second--;
            int b=rex(n-v[x].first,i+1);
            if(b==1)return 1;
            v[x].second++;
        }
    }
    if(i==0)cout<<0;
    return -1;
}
int main(){
    int n,k,s=0;cin>>n>>k;v.resize(k);
    for(int x=0;x<k;++x){
        cin>>v[x].first;v[x].second=2;
        if(s<=n)
            s+=v[x].first*2;}
    if(s<n)
        cout<<-1;
    else{
        sort(v.begin(),v.end());
        rex(n,0);}
}

vector<pair<int,short>> v - вектор для монет first это ценность монеты second количество монет.

Во время заполнения вектора подсчитываю сумму всех монет если сумма меньше суммы N вывожу -1.

В противном случае сортирую вектор и вызываю функцию.(параметры функции n сумма i количество используемых монет)

В функции перебераю вектор с конца тк там самые большие по ценности монеты для нахождения минимального кол монет лучше начать с больших чисел .В теле цикла проверяю присутствуют ли монеты данной ценности если да то отнимают 1 от их количества и вызываю функцию с новой суммой и количеством используемых монет +1 функция возвращает 1 если цель достигнута(сумма N заплачена) и -1 сумма N переплачена если 1 выходим из рекурсия если -1 пробуем дальше пока не пройдём весь вектор.

Неправильный ответ на 14 тесте

Ссылка на задачу: https://acmp.ru/index.asp?main=task&id_task=153

Ответы

▲ 4Принят

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

Вот такой код проходит на ура...

#include <vector>
#include <algorithm>
#include <iostream>

using namespace std;

int counts(int * a, int m, int n, int cnt)
{
    if (n == 0) return cnt;
    if (n <  0) return  -1;
    if (m == 0) return  -1;
    int c2 = counts(a+1,m-1,n-2**a,cnt + 2);
    int c1 = counts(a+1,m-1,n-*a,cnt + 1);
    int c0 = counts(a+1,m-1,n,cnt);
    vector<int> b;
    if (c2 >= 0) b.push_back(c2);
    if (c1 >= 0) b.push_back(c1);
    if (c0 >= 0) b.push_back(c0);
    if (b.size()) return *min_element(b.begin(),b.end());
    return -1;
}


int main(int argc, char * argv[])
{
    int n, m;

    cin >> n >> m;
    int a[15], sum = 0;
    for(int i = 0; i < m; ++i) { cin >> a[i]; sum += 2*a[i]; }
    if (sum < n) cout << -1;
    else
    {
        sort(a,a+m,greater<int>());
        int c = counts(a,m,n,0);
        cout << (c < 0 ? 0 : c);
    }
}

Update

Отвечаю сразу на оба комментария. Когда вот так вдруг приходит задачка, то как правило, готового решения — так, чтоб идеально, эффективно и т.д. — просто нет.

Я не буду приводить все варианты кода, которые писал, просто расскажу, как делал и почему код получился такой, какой он есть. Так сказать, верну леса к построенному зданию.

Разобраться в заданном вопросе коде было, гм... трудновато. Но направление было задано: рекурсия. OK, тем более того, 15 номиналов монет никаких проблем в этом смысле не предвещают.

Сначала набросал main(), с вектором. Но что такое 15? только возиться лишнего. Перебросил на массив.

Начал писать рекурсию. Первая идея — раз надо минимальное количество монет — начинать с самых больших номиналов a la жадный алгоритм. Так что сразу отсортируем...

Теперь, как же должна работать "жадная" рекурсия? Начнем с условия прекращения. Если оставшаяся сумма — 0, значит, мы получили что хотели. Если меньше нуля — тупик. Такой же тупик и если больше нуля, но монет уже нет. Кажется, выход из рекурсии расписан...

Теперь о самой рекурсии... сначала пробуем взять 2 монеты и посмотреть, получится ли получить сумму, меньшую на удвоенный номинал монеты, остальными монетами. Получилось? Возвращаем. Нет? переходим к плану "Б" — одной взятой монете.

Запускаем, пролет на 6 варианте. Перечитываю условие — блин, не обратил внимания, что отдельно обрабатывается случай, когда сумма монет меньше требуемой... Дописал main().

Запуск, пролет на 14 варианте. Пересматриваю рекурсию. Опять "как же я не дотумкал!" — ведь монеты-то могут быть и не выбраны! Дописываю вариант, когда ни одна монета не взята. Прогон — пролет на... не помню каком варианте, но резко побольше, чем 14.

Значит, что-то не так. Видимо, жадность не работает. В конце концов, в варианте обычной оплаты она тоже работает не для всех наборов номиналов. Дописываем выбор минимального решения из трех вариантов.

Прогон — принято.

Откровенно говоря, на этом размышления о задаче завершены. Она не настолько интересна, чтоб крутить ее математику, не так уж неэффективно работает, чтоб возиться. Так что я просто и не вспомнил о первоначальной сортировке. Хотя ретроспективно — есть легкое ("лёгонькое такое") подозрение, что она может несколько ускорить получение требуемого результата. А может и нет. Но по результатам сдачи кода — как минимум не мешает. Так сказать, как какой-то невостребованный ген в ДНК, оставшийся со времен динозавров :) — ну и пусть себе болтается.

Конечно, это acmp... и можно бы повозиться с сокращением текста — но задача достаточно объемная, в три строки ужать сходу не получится, а дел хватает, тем более что в этом сжатии я особого смысла не вижу, разве что "а я еще и так извратиться могу" :)

Так что по результатам пишу ответ на ruSO и занимаюсь другими делами... теперь еще и закинул решение (давненько не делал, кстати...) на gitlab.

Я ответил на оба вопроса?