Задача на полный перебор не прошла по времени C++
Условие задачи:
Даны N целых чисел X1, X2, …, XN. Требуется расставить между ними знаки «+» и «-» так, чтобы значение получившегося выражения было равно заданному целому S.
Входные данные Входной файл INPUT.TXT в первой строке содержит числа N и S. В следующей строке располагается N чисел, разделенных пробелом. Ограничения: 2 ≤ N ≤ 24, 0 ≤ Xi ≤ 5*10^7, -10^9 ≤ S ≤ 10^9.
Выходные данные В выходной файл OUTPUT.TXT выведите «No solution», если такой результат получить невозможно, иначе выведите получившееся равенство. Если решение не единственное, выведите любое.
Ну первые мои мысли при данной задаче: N у нас достаточно маленькое, а значит вполне можно перебрать все возможные случаи расстановок знаков. Ну и методом "вдумчивого взгляда" можно понять, что жадный алгоритм тут придумать невозможно. Поэтому я реализовал полный перебор:
#include<iostream>
#include <string>
#include <vector>
using namespace std;
typedef long long ll;
string ans;
//функция генерации всех возможных сумм
void generate(const ll& need, ll sum_now, const vector<ll>& a, ll i, string str)
{
if (i == a.size())
{
if (need == sum_now)
{
ans = str;
}
}
if (i < a.size())
{
generate(need, sum_now + a[i], a, i + 1, str + "+" + to_string(a[i]));
generate(need, sum_now - a[i], a, i + 1, str + "-" + to_string(a[i]));
}
}
int main()
{
ll n, s;
cin >> n >> s;
vector<ll> a(n);
for (auto& x : a)
cin >> x;
//a[0] может быть только положительным, поэтому сразу вставляем его в сумму
generate(s, a[0], a, 1, to_string(a[0]));
if (ans.empty())
cout << "No solution" << endl;
else
cout << ans + "=" + to_string(s) << endl;
}
И данное решение не прошло по времени. И не совсем понятно почему. Всего вариантов перебора 2^23 < 10^7. По идее должно быть быстро. Встает два вопроса:
Можно ли как-то в случае нахождения верного ответа сказать программе завершить все вызовы рекурсий сразу и закончить работу функции? У меня конечно есть мысль, что можно написать return, но по факту мы же только 1 из вызывов завершаем? Будет ли завершать эта версия выполнять это:
void generate(const ll& need, ll sum_now, const vector<ll>& a, ll i, string str)
{
if (i == a.size())
{
if (need == sum_now)
{
ans = str;
return;
}
}
if (i < a.size())
{
generate(need, sum_now + a[i], a, i + 1, str + "+" + to_string(a[i]));
generate(need, sum_now - a[i], a, i + 1, str + "-" + to_string(a[i]));
}
}
Естественно по идее данная оптимизация не сильно поможет, потому что самая долгая работа все равно будет в случае если должно вывестись "No solution" а значит перебор всех случаев неизбежен. Как можно оптимизировать мой алгоритм?
Встает вопрос, существует ли итеративное решение к данной задаче, а не рекурсивное? Просто мыслей к нему нет никаких, если дадите подсказки буду рад.
Код решения Harry, который тоже чуть чуть не прошел (см ответ Harry и комментарии к нему):
#include<iostream>
#include <string>
#include <vector>
using namespace std;
typedef long long ll;
int main()
{
ll n, s;
cin >> n >> s;
vector<ll> a(n);
for (auto& x : a)
cin >> x;
int M = 1 << (n - 1);
for (size_t i = 0; i < M; i++)
{
ll sum = a[0];
int m = i;
for (size_t j = 1; j < n; j++)
{
if (m & 1)
sum += a[j];
else
sum -= a[j];
m >>= 1;
}
if (sum == s)
{
cout << a[0];
m = i;
for (size_t j = 1; j < n; j++)
{
if (m & 1)
cout << "+" << a[j];
else
cout << "-" << a[j];
m >>= 1;
}
cout << "=" << s;
return 0;
}
}
cout << "No solution";
}
Ответы (2 шт):
Вот, полный перебор, даже без Грея...
По времени вполне укладывается. Просто не нужны никакие строки!
#include <vector>
#include <iostream>
using namespace std;
int main(int argc, char * argv[])
{
vector<int> x;
int N, S;
cin >> N >> S;
for(int i = 0; i < N; ++i)
{
int z;
cin >> z;
x.push_back(z);
}
unsigned int M = 1;
M <<= N-1;
for(unsigned int k = 0; k < M; ++k)
{
unsigned int m = k;
int sum = x[0];
for(int j = 1; j < N; ++j)
{
if (m&1) sum += x[j]; else sum -= x[j];
m >>= 1;
}
if (sum == S)
{
m = k;
cout << x[0];
for(int j = 1; j < N; ++j)
{
if (m&1)
{
cout << "+" << x[j];
}
else cout << "-" << x[j];
m >>= 1;
}
cout << "=" << S << "\n";
return 0;
}
}
cout << "No solution\n";
}
P.S. Писано бегом, так что не оптимизировано и с определенными "затычками"...
Вы всё сделали правильно, кроме двух вещей: не надо было составлять строку для ответа в процессе поиска. Строковые операции сопряжены с выделением памяти и копированием её на каждом вызове.
Вторую неправильную вещь вы сами упомянули - нет нормальной остановки в момент когда решение найдено.
Код ниже обходит обе трудности, возвращая успех/не успех при поиске и печатая ответ кусочками в случае успеха:
#include <iostream>
int x[25];
bool search(int i, int s) {
if (i == 0) {
if (x[0] == s) {
std::cout << x[0];
return true;
}
return false;
}
if (search(i - 1, s - x[i])) {
std::cout << '+' << x[i];
return true;
}
if (search(i - 1, s + x[i])) {
std::cout << '-' << x[i];
return true;
}
return false;
}
int main() {
int n, s;
std::cin >> n >> s;
for (int i = 0; i < n; ++i) {
std::cin >> x[i];
}
if (search(n - 1, s)) {
std::cout << "=" << s << '\n';
} else {
std::cout << "No solution\n";
}
}
$ time echo -e "24 24\n1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1" | ./a.out 1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1=24 real 0m0.002s user 0m0.000s sys 0m0.000s $ time echo -e "24 25\n1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1" | ./a.out No solution real 0m0.055s user 0m0.052s sys 0m0.004s
P.S. В задаче сказано что X_i целые. Следовательно, там может быть ноль и отрицательные значения. Моя программа в этом случае печатает странное:
$ echo -e "2 0\n-1 -1" | ./a.out -1--1=0 $ echo -e "2 -2\n-1 -1" | ./a.out -1+-1=-2