Форум то тематический. Очень трудно придумать задачи, годные для выполнения на МК и не требующие дополнительных модулей, блоков и т.д. И в то же время и про программную составляющую не забыть, сделав ее достаточной сложности.
Можно придумать задачу на принципы работы со входящими и исходящими сигналами, без внешних источников и приёмников, т.е с имитацией. Программно на ардуино это осуществимо.
ПС: чёт у меня "очучение", что у меня часовой пояс ближе к Москве, чем у остальных.
Можно придумать задачу на принципы работы со входящими и исходящими сигналами, без внешних источников, т.е с их имитацией. Программно на ардуино это осуществимо.
Честно говоря, даже предложенная Петровичем задача вызвала у меня, минимум, одну отрицательную эмоцию: задачи подобного типа должны брать входные данные из файла, а не из включенного в код массива. Так что "портирование" данной задачи на Ардуино не обошлось без явных потерь.
А в этой связи родилась мысль: а так ли уж необходимо, чтобы задачи на программирование были непосредственно адаптированы к Ардуино? Может, интерес вызовет и задачи, ориентированные на ПК?
Ну и по поводу Ардуино: здесь, безусловно, интерес представляют не только (а, возможно, и не столько) задачи на программирование, но и задачи на умелое использование аппаратных ресурсов контроллера. И ограничение "без внешних цепей" тоже представляется чрезмерно жестким: вряд ли требование наличия самой стандартной периферии: нескольких кнопок, светодиодов, потенциометров или дисплея 1602 может вызвать трудности для обитателей данного форума.
Для меня необходимость выполнить задачу на заданной платформе составила главную изюминку конкурса. Это форум ардуино и потому я за эадачки для Мк, а не для пк
Ну и по поводу Ардуино: здесь, безусловно, интерес представляют не только (а, возможно, и не столько) задачи на программирование, но и задачи на умелое использование аппаратных ресурсов контроллера. И ограничение "без внешних цепей" тоже представляется чрезмерно жестким: вряд ли требование наличия самой стандартной периферии: нескольких кнопок, светодиодов, потенциометров или дисплея 1602 может вызвать трудности для обитателей данного форума.
Тоже согласен, у каждого ардунщика должен быть минимальный набор, это тоже залог для будущих конкурсов.
Для меня необходимость выполнить задачу на заданной платформе составила главную изюминку конкурса. Это форум ардуино и потому я за эадачки для Мк, а не для пк
Ну, изюминка здесь исключительно в ограничении на объем RAM. В принципе, тоже важно. Вопрос: будет ли экономное использование памяти учитываться при оценке.
Коллеги, удалось посмотреть все три программы, присланные для участия в конкурсе. Буду смотреть ещё, работа не закончена, но пока могу сказать следующее:
Из трех программ пока не удалось получить неверный результат только от программы от Kolyn (но я не оставляю попыток :-))). Так что у нас есть лидер! У остальных, к сожалению неверные результаты проскакивают. Чтобы не быть голословным:
AndreyD, проверьте на вот таком тесте:
static Spanner spanners[] = {
{8, 10, 8700},
{15, 9, 8196},
{12, 16, 6869},
{11, 8, 3652},
{9, 12, 7345},
{11, 10, 3749},
{10, 9, 7685},
{8, 10, 2293},
{15, 9, 8794},
{15, 12, 4720},
{14, 9, 6164},
{16, 9, 2700},
{16, 8, 9370}
};
//////////// ВАШЕ РЕШЕНИЕ ///////////////
2. 12-16: 68 руб. 69 коп.
4. 11-8: 36 руб. 52 коп.
6. 8-10: 22 руб. 93 коп.
7. 15-12: 47 руб. 20 коп.
8. 14-9: 61 руб. 64 коп.
Стоимость набора: 236 руб. 98 коп.
//////////// БОЛЕЕ ДЕШЕВОЕ РЕШЕНИЕ ///////////////
1. 8-10: 22 руб. 93 коп.
2. 16-9: 27 руб. 0 коп.
3. 11-8: 36 руб. 52 коп.
5. 15-12: 47 руб. 20 коп.
11. 14-9: 61 руб. 64 коп.
Стоимость набора: 195 руб. 29 коп.
B707, посмотрите на вот такой тест. Решение получается пустым:
Это не единственные тесты, на которых работает неверно, но я предполагаю, что все они связаны с одной и той же ошибкой, потому остальные не выкладываю. Если это поможет в поиске ошибки - могу выложить.
static Spanner spanners[] = {
{8, 10, 8700},
{15, 9, 8196},
{12, 16, 6869},
{11, 8, 3652},
{9, 12, 7345},
{11, 10, 3749},
{10, 9, 7685},
{8, 10, 2293},
{15, 9, 8794},
{15, 12, 4720},
{14, 9, 6164},
{16, 9, 2700},
{16, 8, 9370}
};
//////////// ВАШЕ РЕШЕНИЕ ///////////////
2. 12-16: 68 руб. 69 коп.
4. 11-8: 36 руб. 52 коп.
6. 8-10: 22 руб. 93 коп.
7. 15-12: 47 руб. 20 коп.
8. 14-9: 61 руб. 64 коп.
Стоимость набора: 236 руб. 98 коп.
//////////// БОЛЕЕ ДЕШЕВОЕ РЕШЕНИЕ ///////////////
1. 8-10: 22 руб. 93 коп.
2. 16-9: 27 руб. 0 коп.
3. 11-8: 36 руб. 52 коп.
5. 15-12: 47 руб. 20 коп.
11. 14-9: 61 руб. 64 коп.
Стоимость набора: 195 руб. 29 коп.
Признаю поражение.
Ошибка в строках 151-205 моего кода (блок // Удаляем более дорогие ключи с одним оставшимся размером, а если размера только два, второй ключ добовляем в результирующий набор, помечая размеры на удаление из этого ключа.)
Не до конца продумал логику.
Отсев дублей и последующий перебор сочетанями без повторений, видимо, более правильное решение. Так что, думаю, что у Kolyn все ответы будут правильными.
Попробуйте, пожалуйста, вот такой вариант на наборах, которые выдавали ошибочный ответ:
// Тип данных для хранения ключа
struct Spanner : public Printable {
uint32_t price;// цена в копейках
uint8_t size1; // размер 1
uint8_t size2; // размер 2
bool included; // если true, то включается в результирующий набор или дубли
Spanner(const uint8_t s1, const uint8_t s2, const uint32_t p, const bool ini = false)
: price(p), size1(s1), size2(s2), included(ini) {}
size_t printTo(Print & p) const {
size_t res = p.print(size1);
res += p.print(F("-"));
res += p.print(size2);
res += p.print(F(": "));
res += printPrice(p, price);
return res;
}
static size_t printPrice(Print & p, const uint32_t priceCop) {
size_t res = p.print(priceCop / 100);
res += p.print(F(" руб. "));
res += p.print(priceCop % 100);
res += p.print(F(" коп."));
return res;
}
};
// Массив всех ключей
// Ключей в массиве не больше 100
// Размеры в одном ключе целочисленные, не равны 0, не равны между собой, и каждый не больше 255
// При наличии пресекающихся по размерам ключей в кол-ве больше 30 вычисления могут занять длительное время
static Spanner spanners[] = {
{8, 10, 8700},
{15, 9, 8196},
{12, 16, 6869},
{11, 8, 3652},
{9, 12, 7345},
{11, 10, 3749},
{10, 9, 7685},
{8, 10, 2293},
{15, 9, 8794},
{15, 12, 4720},
{14, 9, 6164},
{16, 9, 2700},
{16, 8, 9370}
};
static constexpr uint8_t totalSpanners = sizeof(spanners) / sizeof(spanners[0]);
uint8_t sizes[totalSpanners * 2], cSizes[totalSpanners * 2];
Spanner tempS[] = {{0,0,0,0}};
bool flag1=0, flag2=0, flag3=0;
uint8_t totalSpanners1=0, allSizes=0;
// Функция перебора сочетаний без повторений
bool nextSet(uint8_t *indexKeys, int16_t n, int16_t m) {
for (int16_t i = m - 1; i >= 0; i--) {
if (indexKeys[i] < n - m + i) {
indexKeys[i]++;
for (int16_t j = i + 1; j < m; j++)
indexKeys[j] = indexKeys[j - 1] + 1;
return 1;
}
}
return 0;
}
void doCalculations(void) {
memset(sizes, 0, sizeof(sizes[0]) * (totalSpanners * 2)); // Обнуление массива размеров.
memset(cSizes, 0, sizeof(cSizes[0]) * (totalSpanners * 2)); // Обнуление массива кол-ва одинаковых размеров.
// Поиск более дорогих дублей и удаление их сдвигом из массива структур
totalSpanners1 = totalSpanners;
for (uint8_t i=0; i < totalSpanners1; i++)
for (uint8_t j=i+1; j < totalSpanners1; j++)
if ((((spanners[i].size1 == spanners[j].size1) && (spanners[i].size2 == spanners[j].size2)) ||
((spanners[i].size1 == spanners[j].size2) && (spanners[i].size2 == spanners[j].size1)))) {
if (spanners[i].price >= spanners[j].price) {
for (uint8_t k = i; k < totalSpanners1 - 1; k++)
spanners[k] = spanners[k + 1];
i--;
} else {
for (uint8_t k = j; k < totalSpanners1 - 1; k++)
spanners[k] = spanners[k + 1];
j--;
}
totalSpanners1--;
}
// Если все дубли выводим результат с одним ключём
if (totalSpanners1 == 1) {spanners[0].included = 1; return;}
// Поиск всех размеров ключей и вычисление кол-ва каждого размера
for (uint8_t i = 0; i < totalSpanners1; i++) {
for (uint8_t j=0; j < allSizes; j++) {
if (sizes[j] == spanners[i].size1) {flag1=1; cSizes[j]++;}
if (sizes[j] == spanners[i].size2) {flag2=1; cSizes[j]++;}
}
if (!flag1) {sizes[allSizes] = spanners[i].size1; allSizes++;} else flag1=0;
if (!flag2) {sizes[allSizes] = spanners[i].size2; allSizes++;} else flag2=0;
}
// Добавление ключей с одним размером в результирующий набор
for (uint8_t i = 0; i < allSizes; i++)
if (cSizes[i] == 0)
for (uint8_t j=0; j < totalSpanners1; j++) {
if (spanners[j].included) continue;
if (sizes[i] == spanners[j].size1 || sizes[i] == spanners[j].size2)
spanners[j].included = 1;
}
// Пометка вторых размеров из уже выбранных ключей на удаление
for (uint8_t i=0; i < totalSpanners1; i++)
if (spanners[i].included)
for (uint8_t j = 0; j < allSizes; j++)
if (sizes[j] == spanners[i].size1 || sizes[j] == spanners[i].size2) cSizes[j] = 0;
// Удаление размеров которые уже есть в выбраных ключах
for (uint8_t i = 0; i < allSizes; i++)
if (cSizes[i] == 0) {
for (uint8_t j = i; j < allSizes - 1; j++)
{sizes[j] = sizes[j+1]; cSizes[j] = cSizes[j+1];}
allSizes--; i--;
}
// Удаление ключей у которых оба размера совпадает с размерами уже выбранных ключей.
for (uint8_t i = 0; i < totalSpanners1; i++) {
if (spanners[i].included) continue;
flag1=0;
for (uint8_t j = 0; j < allSizes; j++)
if (sizes[j] == spanners[i].size1 || sizes[j] == spanners[i].size2) flag1=1;
if (!flag1) {
for (uint8_t k = i; k < totalSpanners1 - 1; k++)
spanners[k] = spanners[k + 1];
totalSpanners1--; i--;
spanners[totalSpanners1].included = 0;
}
}
// Убираем уже выбранные ключи в конец массива
for (uint8_t i = 0; i < totalSpanners1; i++)
for (uint8_t j = 0; j < totalSpanners1 - 1; j++)
if (spanners[j].included > spanners[j+1].included) {
tempS[0] = spanners[j];
spanners[j] = spanners[j + 1];
spanners[j + 1] = tempS[0];
}
// Считаем оставшиеся ключи.
for (uint8_t i = 0; i < totalSpanners1; i++)
if (spanners[i].included == 1) totalSpanners1 = i;
//Сортировка оставшихся ключей по цене от большей к меньшей
for (uint8_t i = 0; i < totalSpanners1; i++)
for (uint8_t j = 0; j < totalSpanners1 - 1; j++)
if (spanners[j].price < spanners[j+1].price) {
tempS[0] = spanners[j];
spanners[j] = spanners[j + 1];
spanners[j + 1] = tempS[0];
}
bool stopElem=0;
int16_t n=totalSpanners1;
uint8_t *indexKeys, rezIndexKeys[n], rezM=0, elem=0;
indexKeys = new int8_t[n];
uint64_t rezSumPrice = UINT64_MAX, sumPrice = 0;
// перебор сочетаний индексов массива ключей
// n - кол-во оставшихся не выбранных ключей
// m - от половины оставшихся размеров до кол-ва не выбранных ключей
for (int16_t m=round(((float)allSizes/2)); m <= n; m++) {
for (int16_t i = 0; i < n; i++)
indexKeys[i] = i;
do {
sumPrice = 0; flag1=1;
// Проверка, что сумма сочетания меньше суммы предыдущего выбранного сочетания
for (int16_t i = 0; i < m; i++) {
sumPrice = sumPrice + uint64_t(spanners[indexKeys[i]].price);
if (sumPrice >= rezSumPrice) {flag1 = 0; break;}
}
if (flag1) {
flag2=1;
// Проверка наличия всех оставшихся размеров в сочетании
for (uint8_t i=0; i < allSizes; i++) {
for (uint8_t j=0; j < m; j++) {
if ((spanners[indexKeys[j]].size1 == sizes[i]) || (spanners[indexKeys[j]].size2 == sizes[i])) break;
if (j == m-1)
if ((spanners[indexKeys[j]].size1 != sizes[i]) && (spanners[indexKeys[j]].size2 != sizes[i]))
flag2=0;
}
if (!flag2) break;
}
}
// Если обе проверки пройдены сохраняем результат
if (flag1 && flag2) {
rezSumPrice = sumPrice;
rezM = m;
for (uint8_t j = 0; j < m; j++)
rezIndexKeys[j] = indexKeys[j];
}
} while (nextSet(indexKeys, n, m));
}
// Добавляем ключи выбранные на основе перебора в результирующий набор
for (uint8_t i = 0; i < rezM; i++) {
spanners[rezIndexKeys[i]].included = 1;
}
}
void printResults(void) {
Serial.println(F("Ключи"));
uint32_t total = 0;
for (uint8_t i = 0; i < totalSpanners; i++) {
if (spanners[i].included) {
Serial.print(i + 1);
Serial.print(". ");
Serial.println(spanners[i]);
total += spanners[i].price;
}
}
Serial.print(F("Стоимость набора: "));
Spanner::printPrice(Serial, total);
Serial.println();
}
void setup(void) {
Serial.begin(9600);
const uint32_t start = millis();
if (totalSpanners > 100) {Serial.println(F("Колличество ключей больше 100. Уменьшите их колличество.")); return;}
doCalculations();
const uint32_t duration = millis() - start;
printResults();
Serial.print(F("Время расчёта: "));
Serial.print(duration);
Serial.println(F(" миллисекунд"));
}
void loop(void) {}
Это не единственные тесты, на которых работает неверно, но я предполагаю, что все они связаны с одной и той же ошибкой, потому остальные не выкладываю. Если это поможет в поиске ошибки - могу выложить.
не надо, ошибку нашел сразу.
Правильный ответ:
=== Span selection ===
Span 10x11
Span 8x14
Span 12x15
Total value = 128.28
Calculation time = 2.320 ms
Обидно, что в версии, что закончил через 3 часа после закрытия турнира - этой ошибки нет.
Но тут уж ничего не попишешь. Выложил я на конкурс эту версию, поэтому что о других говорить.
Вы ж вроде, нас с дедом в этом деле никогда не поддерживали!
это потому что у меня повода серьезного не было :)
начинать надо было с той моей задачи про бочку, кою решил (почти) только один человек на форуме (кроме дракулы, но я его решение даже понять не в состоянии, не то, что проверить) :-)))
Для конкурса крайне важно, чтобы Организатор и Жюри пользовались безусловным авторитетом в данной области... иначе итогом будет просто срач. как и сложилось в твоем случае.
Попробуйте сами на вот этих пяти тестах. Для проверки результатов используйте любую из программ коллег - с этими пятью они обе справляются.
Да приведённый мной код в сообщении #360 считает эти тесты правильно, НО проигрывает по времени коду kolyn , т.е. без правильной реализации моего пятого пункта словесного алгоритма я бы всё равно не выиграл.
Пятый пункт:
Далее могут присутствовать ключи, у которых один размер исключен, можно сравнить его стоимость с остальными ключами с таким же размером и если его цена больше то исключить его. При этом, если кол-во ключей с этим размером был равен 2, то более дешёвый ключ становиться уникальным. А его второй размер исключается из дальнейшего перебора.
Обработка результатов производилась в два этапа. На первом этапе я просто смотрел на программы и запускал их, чтобы проверить свои предположения об их поведении (правильно ли я их понимаю). На этом этапе обнаружилось, что две из трёх присланных программ содержат ошибки, которые проявляются на некоторых тестах.
Ну, как бы с победителем стало всё ясно, но это формальная сторона вопроса. В реальности же, ошибки мне показались непринципиальными (легко исправимыми), а идеи, заложенные в программах весьма интересными, поэтому я решил продолжить исследование (считая, что ошибки как бы исправлены) и выяснить вопросы скорости, тем более, что тем, кто предложит вариант быстрее моего, был обещать отдельный приз.
Последнее (предложение приза) было вполне реальным и я надеялся, что его кто-то выиграет, т.к. изначально я не планировал писать свою программу "на все деньги", т.е. добиваться того, чтобы она была как можно более эффективной. Вместо этого я планировал использовать только очевидные и бесспорные оптимизации и стандартные, "вездеупотребительные" методы. Т.е. я хотел написать учебный материал по общим методам решения задач такого рода и не более того. Такая программа заведомо должна проигрывать специализированным программам, использующим частные методы "под задачу". Её ценность в демонстрации общих методов, которые применимы для всех задач. Я рассчитывал, что кто-нибудь обязательно напишет более быстрое решение. Считаю, что в действительности так и получилось, несмотря на то, что формально это не так (см. ниже).
Таким образом, на втором этапе я нагенерил кучу случайных тестов, а именно - девяносто тестов с семью ключами, пятьдесят тестов с тринадцатью ключами и тридцать два теста с семнадцатью ключами и прогнал через эти 172 теста все программы, включая и свою - внеконкурсную.
Все использованные тесты приведены ниже (повторяю, они получились путём генерации псевдослучайных чисел).
Результат прогона всех четырёх программ представлен в таблице ниже. В первых двух столбцах количество ключей и номер теста - эта пара уникальна, так что Вы легко можете найти нужный тест в выложенных выше листингах. Цена в копейках, а время в микросекундах. На тех тестах, на которых та или иная программа выдала неверный результат, вручную проставлено время 10 000 000 мкс., что гарантирует, что они окажутся "хуже" любого правильного результата. Всего таких неудачных тестов 28. Пять у программы от AndreyD и 23 у программы от b707.
Ключей
№ теста
Ник
Цена
Время
7
1
EP
26189
304
b707
26189
364
AndreyD
26189
424
Kolyn
26189
596
7
2
EP
21209
324
AndreyD
21209
400
b707
21209
404
Kolyn
21209
568
7
3
b707
26512
396
EP
26512
468
AndreyD
26512
504
Kolyn
26512
816
7
4
b707
16493
432
EP
16493
536
AndreyD
16493
552
Kolyn
16493
924
7
5
EP
34161
356
b707
34161
400
AndreyD
34161
516
Kolyn
34161
716
7
6
b707
14694
396
AndreyD
14694
456
EP
14694
460
Kolyn
14694
752
7
7
EP
23013
888
b707
23013
952
Kolyn
23013
1160
AndreyD
23013
1548
7
8
EP
25773
344
b707
25773
396
AndreyD
25773
464
Kolyn
25773
648
7
9
EP
19302
540
Kolyn
19302
852
b707
19302
856
AndreyD
19302
880
7
10
b707
15319
376
AndreyD
15319
440
EP
15319
464
Kolyn
15319
752
7
11
b707
28998
404
AndreyD
28998
484
EP
28998
508
Kolyn
28998
804
7
12
b707
31260
404
AndreyD
31260
508
EP
31260
508
Kolyn
31260
808
7
13
b707
23360
420
AndreyD
23360
548
EP
23360
656
Kolyn
23360
928
7
14
b707
26715
372
EP
26715
380
AndreyD
26715
392
Kolyn
26715
588
7
15
b707
9942
364
AndreyD
9942
392
EP
9942
488
Kolyn
9942
684
7
16
EP
21793
272
b707
21793
372
AndreyD
21793
420
Kolyn
21793
592
7
17
b707
25062
420
AndreyD
25062
584
EP
25062
620
Kolyn
25062
964
7
18
b707
19251
376
AndreyD
19251
384
EP
19251
480
Kolyn
19251
708
7
19
b707
28180
396
AndreyD
28180
492
EP
28180
524
Kolyn
28180
808
7
20
EP
23919
340
b707
23919
384
AndreyD
23919
480
Kolyn
23919
664
7
21
EP
17576
812
b707
17576
984
Kolyn
17576
1184
AndreyD
17576
2292
7
22
b707
12416
428
EP
12416
504
AndreyD
12416
588
Kolyn
12416
976
7
23
EP
21933
652
Kolyn
21933
964
b707
21933
1128
AndreyD
21933
1424
7
24
b707
28013
368
AndreyD
28013
396
EP
28013
532
Kolyn
28013
708
7
25
b707
10115
428
AndreyD
10115
616
EP
10115
848
Kolyn
10115
1444
7
26
AndreyD
22658
360
b707
22658
368
EP
22658
440
Kolyn
22658
564
7
27
b707
24905
400
EP
24905
448
AndreyD
24905
536
Kolyn
24905
804
7
28
EP
18334
680
Kolyn
18334
1052
b707
18334
1252
AndreyD
18334
1396
7
29
EP
27636
332
b707
27636
388
AndreyD
27636
468
Kolyn
27636
628
7
30
b707
24002
400
AndreyD
24002
528
EP
24002
540
Kolyn
24002
816
7
31
b707
17932
372
AndreyD
17932
428
EP
17932
460
Kolyn
17932
752
7
32
b707
17303
424
AndreyD
17303
520
EP
17303
524
Kolyn
17303
844
7
33
EP
18650
840
Kolyn
18650
1196
b707
18650
1312
AndreyD
18650
2536
7
34
EP
19092
812
b707
19092
1268
AndreyD
19092
1636
Kolyn
19092
1776
7
35
EP
10710
524
AndreyD
10710
656
b707
10710
700
Kolyn
10710
732
7
36
AndreyD
16911
376
b707
16911
376
EP
16911
572
Kolyn
16911
684
7
37
EP
12828
1164
Kolyn
12828
1940
AndreyD
12828
4196
b707
0
10000000
7
38
b707
22356
396
AndreyD
22356
504
EP
22356
508
Kolyn
22356
820
7
39
b707
28379
420
AndreyD
28379
464
EP
28379
568
Kolyn
28379
764
7
40
b707
16339
408
AndreyD
16339
460
EP
16339
532
Kolyn
16339
796
7
41
b707
22431
364
AndreyD
22431
416
EP
22431
460
Kolyn
22431
616
7
42
EP
13121
340
b707
13121
368
AndreyD
13121
404
Kolyn
13121
556
7
43
EP
22317
656
AndreyD
22317
760
b707
22317
784
Kolyn
22317
952
7
44
EP
23874
324
b707
23874
400
AndreyD
23874
484
Kolyn
23874
668
7
45
b707
25033
332
EP
25033
348
AndreyD
25033
412
Kolyn
25033
592
7
46
b707
12222
416
AndreyD
12222
532
EP
12222
692
Kolyn
12222
996
7
47
EP
26922
692
b707
26922
892
Kolyn
26922
1016
AndreyD
26922
1064
7
48
EP
34200
356
b707
34200
400
AndreyD
34200
536
Kolyn
34200
672
7
49
b707
18079
404
AndreyD
18079
504
EP
18079
592
Kolyn
18079
948
7
50
EP
18303
348
b707
18303
364
AndreyD
18303
412
Kolyn
18303
564
7
51
EP
9691
368
AndreyD
9691
376
b707
9691
388
Kolyn
9691
664
7
52
b707
23678
376
AndreyD
23678
460
EP
23678
548
Kolyn
23678
796
7
53
b707
23758
388
EP
23758
456
AndreyD
23758
504
Kolyn
23758
784
7
54
EP
31113
332
b707
31113
384
AndreyD
31113
452
Kolyn
31113
588
7
55
b707
25126
412
AndreyD
25126
464
EP
25126
600
Kolyn
25126
756
7
56
b707
28129
408
EP
28129
504
AndreyD
28129
544
Kolyn
28129
936
7
57
EP
18515
324
b707
18515
364
AndreyD
18515
488
Kolyn
18515
664
7
58
EP
31486
340
b707
31486
388
AndreyD
31486
496
Kolyn
31486
696
7
59
b707
20960
412
AndreyD
20960
472
EP
20960
568
Kolyn
20960
796
7
60
EP
15936
304
b707
15936
372
AndreyD
15936
380
Kolyn
15936
576
7
61
EP
21170
712
b707
21170
772
Kolyn
21170
840
AndreyD
21170
892
7
62
EP
12555
640
b707
12555
752
Kolyn
12555
1032
AndreyD
12555
1120
7
63
b707
30270
396
EP
30270
488
AndreyD
30270
552
Kolyn
30270
836
7
64
EP
12395
744
b707
12395
808
Kolyn
12395
988
AndreyD
12395
1052
7
65
b707
26402
416
AndreyD
26402
492
EP
26402
524
Kolyn
26402
796
7
66
EP
27754
288
b707
27754
388
AndreyD
27754
432
Kolyn
27754
616
7
67
EP
13991
744
Kolyn
13991
1016
b707
13991
1284
AndreyD
13991
1396
7
68
b707
27338
400
AndreyD
27338
536
EP
27338
564
Kolyn
27338
1000
7
69
EP
25819
772
Kolyn
25819
1152
b707
25819
1344
AndreyD
25819
1528
7
70
b707
27118
384
AndreyD
27118
424
EP
27118
648
Kolyn
27118
728
7
71
b707
18119
400
EP
18119
456
AndreyD
18119
484
Kolyn
18119
788
7
72
b707
22191
380
AndreyD
22191
424
EP
22191
556
Kolyn
22191
752
7
73
b707
31555
364
EP
31555
372
AndreyD
31555
456
Kolyn
31555
676
7
74
b707
18531
376
AndreyD
18531
424
EP
18531
484
Kolyn
18531
752
7
75
b707
20164
416
AndreyD
20164
488
EP
20164
584
Kolyn
20164
784
7
76
b707
12181
380
AndreyD
12181
396
EP
12181
456
Kolyn
12181
708
7
77
EP
21247
776
b707
21247
828
Kolyn
21247
1020
AndreyD
21247
1088
7
78
EP
23255
308
b707
23255
372
AndreyD
23255
460
Kolyn
23255
636
7
79
EP
23169
328
b707
23169
416
AndreyD
23169
424
Kolyn
23169
604
7
80
EP
10314
376
b707
10314
380
AndreyD
10314
388
Kolyn
10314
576
7
81
EP
14608
1112
Kolyn
14608
1620
b707
14608
1660
AndreyD
14608
2400
7
82
EP
19401
368
b707
19401
372
AndreyD
19401
436
Kolyn
19401
616
7
83
b707
19738
388
AndreyD
19738
392
EP
19738
560
Kolyn
19738
684
7
84
b707
42278
408
EP
42278
496
AndreyD
42278
544
Kolyn
42278
868
7
85
b707
25967
424
EP
25967
532
AndreyD
25967
612
Kolyn
25967
888
7
86
EP
33368
504
b707
33368
800
AndreyD
33368
880
Kolyn
33368
992
Ключей
№ теста
Ник
Цена
Время
7
87
EP
25832
504
b707
25832
760
AndreyD
25832
780
Kolyn
25832
864
7
88
b707
17901
420
AndreyD
17901
500
EP
17901
580
Kolyn
17901
808
7
89
b707
21109
412
AndreyD
21109
504
EP
21109
520
Kolyn
21109
804
7
90
b707
16450
400
AndreyD
16450
484
EP
16450
624
Kolyn
16450
800
13
1
b707
22376
632
AndreyD
22376
808
EP
22376
940
Kolyn
22376
1796
13
2
EP
14942
3772
Kolyn
14942
18700
AndreyD
14942
30264
b707
0
10000000
13
3
b707
24321
5000
EP
24321
5048
Kolyn
24321
28656
AndreyD
24321
31188
13
4
EP
19261
2768
b707
19261
4672
Kolyn
19261
10024
AndreyD
19261
14916
13
5
EP
19245
3680
Kolyn
19245
9444
AndreyD
19245
30260
b707
0
10000000
13
6
b707
27940
736
AndreyD
27940
1244
EP
27940
1492
Kolyn
27940
4792
13
7
b707
15761
1236
EP
15761
1824
AndreyD
15761
4648
Kolyn
15761
5768
13
8
EP
14744
1464
b707
14744
3160
Kolyn
14744
3204
AndreyD
14744
4164
13
9
b707
19529
1776
EP
19529
2244
Kolyn
19529
15348
AndreyD
23698
10000000
13
10
b707
15946
668
AndreyD
15946
1012
EP
15946
1412
Kolyn
15946
2192
13
11
EP
16322
1520
b707
16322
2456
AndreyD
16322
2972
Kolyn
16322
4736
13
12
b707
38791
3212
EP
38791
3724
Kolyn
38791
13820
AndreyD
38791
19136
13
13
EP
25795
1012
b707
25795
1060
Kolyn
25795
1844
AndreyD
26337
10000000
13
14
b707
15152
2444
EP
15152
2644
Kolyn
15152
9076
AndreyD
15152
14972
13
15
b707
30318
1172
EP
30318
1252
AndreyD
30318
1324
Kolyn
30318
2308
13
16
EP
20260
2204
b707
20260
2684
Kolyn
20260
16836
AndreyD
20260
17272
13
17
EP
21896
3740
b707
21896
5516
Kolyn
21896
14676
AndreyD
21896
15900
13
18
b707
28810
2112
EP
28810
2916
AndreyD
28810
3332
Kolyn
28810
12116
13
19
b707
16334
3236
EP
16334
4480
Kolyn
16334
17668
AndreyD
16334
54204
13
20
EP
15905
2508
b707
15905
3808
Kolyn
15905
7168
AndreyD
15905
8748
13
21
EP
25866
1064
Kolyn
25866
2484
b707
25866
3700
AndreyD
26364
10000000
13
22
EP
15954
3052
b707
15954
3604
Kolyn
15954
11024
AndreyD
15954
16908
13
23
EP
21541
1608
AndreyD
21541
2280
b707
21541
2344
Kolyn
21541
7560
13
24
EP
23330
2944
b707
23330
3260
Kolyn
23330
10400
AndreyD
23330
15468
13
25
b707
19243
1656
EP
19243
1940
AndreyD
19243
2196
Kolyn
19243
7168
13
26
b707
25783
1152
EP
25783
1352
AndreyD
25783
1492
Kolyn
25783
3464
13
27
b707
31225
4880
EP
31225
8032
Kolyn
31225
25076
AndreyD
31225
34320
13
28
EP
21505
1628
b707
21505
1820
AndreyD
21505
3248
Kolyn
21505
4620
13
29
b707
16470
1868
EP
16470
2180
AndreyD
16470
3180
Kolyn
16470
7452
13
30
EP
20954
2808
b707
20954
3872
AndreyD
20954
8100
Kolyn
20954
9248
13
31
b707
21548
676
EP
21548
1112
Kolyn
21548
2060
AndreyD
27658
10000000
13
32
EP
27307
2268
b707
27307
2304
AndreyD
27307
3148
Kolyn
27307
6008
13
33
b707
22582
660
AndreyD
22582
924
EP
22582
1000
Kolyn
22582
1836
13
34
b707
20750
1116
EP
20750
1208
AndreyD
20750
1624
Kolyn
20750
3496
13
35
b707
13535
1760
EP
13535
2016
AndreyD
13535
2328
Kolyn
13535
8056
13
36
b707
23321
696
EP
23321
972
AndreyD
23321
1008
Kolyn
23321
1868
13
37
AndreyD
16124
1028
EP
16124
1148
b707
16124
1160
Kolyn
16124
2104
13
38
b707
27489
1184
EP
27489
1268
AndreyD
27489
1320
Kolyn
27489
2264
13
39
EP
25851
1036
b707
25851
1220
AndreyD
25851
1356
Kolyn
25851
1972
13
40
b707
28188
1220
AndreyD
28188
1356
EP
28188
2036
Kolyn
28188
5456
13
41
EP
14926
1496
b707
14926
1984
Kolyn
14926
4236
AndreyD
14926
8112
13
42
b707
32591
768
AndreyD
32591
1296
EP
32591
3396
Kolyn
32591
13368
13
43
EP
20018
7296
Kolyn
20018
39364
AndreyD
20018
70020
b707
0
10000000
13
44
EP
17724
3200
b707
17724
3636
AndreyD
17724
9036
Kolyn
17724
26676
13
45
b707
30067
632
AndreyD
30067
888
EP
30067
920
Kolyn
30067
1744
13
46
b707
32757
608
AndreyD
32757
924
EP
32757
1292
Kolyn
32757
2812
13
47
b707
26759
716
EP
26759
1000
Kolyn
26759
1928
AndreyD
27213
10000000
13
48
b707
19299
600
AndreyD
19299
832
EP
19299
944
Kolyn
19299
1676
13
49
b707
38548
600
AndreyD
38548
900
EP
38548
1028
Kolyn
38548
2196
13
50
EP
23887
1080
b707
23887
1172
AndreyD
23887
1176
Kolyn
23887
2384
19
1
EP
24206
34452
Kolyn
24206
388268
AndreyD
24206
3998060
b707
0
10000000
19
2
EP
13457
3832
b707
13457
3960
AndreyD
13457
13704
Kolyn
13457
82144
19
3
EP
16296
3744
b707
16296
5688
Kolyn
16296
20236
AndreyD
16296
27200
19
4
EP
19316
26648
Kolyn
19316
296188
AndreyD
19316
1891292
b707
0
10000000
19
5
EP
18673
21764
Kolyn
18673
172464
AndreyD
18673
959648
b707
0
10000000
19
6
b707
22313
6708
EP
22313
7272
AndreyD
22313
33556
Kolyn
22313
41452
19
7
EP
18820
31044
Kolyn
18820
373892
AndreyD
18820
3724912
b707
0
10000000
19
8
b707
19797
1496
AndreyD
19797
2132
EP
19797
4092
Kolyn
19797
24368
19
9
b707
22478
1824
EP
22478
2432
AndreyD
22478
2756
Kolyn
22478
9788
19
10
b707
16321
10188
EP
16321
10600
Kolyn
16321
212944
AndreyD
16321
391404
19
11
EP
20900
18660
Kolyn
20900
239456
AndreyD
20900
1874276
b707
0
10000000
19
12
EP
19850
41884
Kolyn
19850
845376
AndreyD
19850
3914480
b707
0
10000000
19
13
EP
16531
2516
b707
16531
2976
AndreyD
16531
5472
Kolyn
16531
11116
19
14
EP
11405
12372
Kolyn
11405
334964
AndreyD
11405
2887036
b707
0
10000000
19
15
EP
22091
25804
Kolyn
22091
196500
AndreyD
22091
967352
b707
0
10000000
19
16
EP
15463
19068
Kolyn
15463
160368
AndreyD
15463
959876
b707
0
10000000
19
17
b707
22443
7920
EP
22443
12280
AndreyD
22443
35456
Kolyn
22443
88132
19
18
EP
24531
31568
Kolyn
24531
159364
AndreyD
24531
544052
b707
0
10000000
19
19
EP
18356
25276
Kolyn
18356
292772
AndreyD
18356
1020068
b707
0
10000000
19
20
EP
15392
16312
Kolyn
15392
583000
AndreyD
15392
5549240
b707
0
10000000
19
21
b707
23521
5088
EP
23521
9496
AndreyD
23521
62508
Kolyn
23521
65400
19
22
b707
20797
964
EP
20797
1636
AndreyD
20797
1792
Kolyn
20797
3660
19
23
EP
17047
7776
Kolyn
17047
43136
AndreyD
17047
115580
b707
0
10000000
19
24
EP
14375
14436
Kolyn
14375
119536
AndreyD
14375
419760
b707
0
10000000
19
25
EP
15029
10560
Kolyn
15029
148372
AndreyD
15029
793692
b707
0
10000000
19
26
EP
16633
20140
Kolyn
16633
163208
AndreyD
16633
937452
b707
0
10000000
19
27
EP
15051
6080
b707
15051
7364
Kolyn
15051
281380
AndreyD
15051
377280
19
28
EP
22262
41772
Kolyn
22262
314760
AndreyD
22262
2020488
b707
0
10000000
19
29
EP
24191
35968
Kolyn
24191
291924
AndreyD
24191
1971952
b707
0
10000000
19
30
EP
16927
1936
b707
16927
4800
Kolyn
16927
5028
AndreyD
16927
5592
19
31
EP
19484
23512
Kolyn
19484
236240
AndreyD
19484
1930024
b707
0
10000000
19
32
b707
15378
3784
EP
15378
4368
Kolyn
15378
24612
AndreyD
15378
31132
Ну, давайте сравним скорость работы.
Поскольку тест на тест не приходится, время сравнивалось по отдельным тестам, а потом уже обобщалось на "в среднем".
В таблице ниже слева просуммировано сколько раз (из 172 тестов) какая из программ оказывалась на первом, втором, третьем и четвёртом местах по времени исполнения. Справа же приведены интегральные показатели скорости программ участников, полученные таким образом: количество первых место умноженное на 3 (т.к. обошёл трёх соперников) + количество вторых мест, умноженное на 2 (из тех же соображений) + количество третьих мест (четвёртые не учитывались вовсе).
I
II
III
IV
EP
86
39
47
0
b707
83
52
14
23
AndreyD
3
50
82
37
Kolyn
0
31
29
112
EP
383
b707
367
AndreyD
191
Kolyn
91
отсюда сразу же видно, что формально, в среднем моя программа быстрее, но если исправить ошибку в программе b707, то она окажется самой быстродействующей. Ведь 23 в графе "четвёртые места" - это как раз те самые 23 теста с которыми она просто не справилась. На тех тестах, с которыми она справлялась, она была в среднем быстрее всех. Обязательно посмотрите на эвристики, которые там используются, они очень красивые. Очень, надеюсь, что автор выложит ещё и пояснения к ней. Кстати, остальных участников тоже очень прошу выложить пояснения.
ну, а с точки зрения подведения итогов и вручения приза, надо отметить, что программы двух участников - b707 и AndreyD обходили мою на каких-то тестах, а потому им обоим полагается обещанный "спец. приз.".
Таким образом,
1. победителем признаётся Kolyn - единственный из участников, чья программа справилась со всеми тестами.
2. b707 и AndreyD получают специальные призы за то, что их программы оказались быстрее моей на нескольких тестах тестах.
Прошу всех троих прислать мне на elilitko@mail.ru координаты для отправки призов.
P.S.
собственную программу выкладываю, но хочу ещё снабдить её пояснениями, чтобы превратить в учебный материал, но на это уже нет сил - завтра пояснения выложу. Если кто-то захочет её скорость измерять на своих тестах, не забудьте все печати закомментировать. Программа состоит из 4-х файлов. Для печати отладочной информации нужна библиотека Printing.
Файл .ino
#include <Printing.h>
typedef uint8_t TSize;
typedef uint16_t TPrice;
#include "Spanner.h"
static Spanner spanners[] = {
{1, 8 , 4},
{4, 6 , 10},
{6, 12, 123},
{2, 6 , 12},
{8, 1 , 2},
{6, 10 , 3},
{20, 9 , 3},
{20, 17 , 3},
{20, 17 , 4},
{1, 8 , 3},
{2, 8 , 13},
{8, 6 , 38208}
};
static uint8_t totalSpanners = sizeof(spanners) / sizeof(spanners[0]);
static inline void printSpanners(const __FlashStringHelper * title) {
Serial.println(title);
for (uint8_t i = 0; i < totalSpanners; i++) Serial.println(spanners[i]);
Serial.println(F("-------"));
}
#include "Sizes.h"
#include "Solution.h"
// Рабочее и "текущее лучшее" решения
Solution currentSolution(0), bestSolution;
//
// Удаляем дубликаты
//
uint8_t removeDuplicates(void) {
Spanner::sortBySpanners(spanners, totalSpanners);
uint8_t removedItemsCount = 0;
for (uint8_t i = 1; i < totalSpanners; i++) {
for (; i < totalSpanners && spanners[i] == spanners[i-1]; i++, removedItemsCount++)
spanners[i].state = Spanner::stateDuplicate;
}
return removedItemsCount;
}
//
// Создаём массив всех размеров Solution::allSizes
//
bool createSizesArray() {
uint8_t minSize = 255, maxSize = 0;
for(uint8_t i = 0; i < totalSpanners; i++) {
const Spanner & spanner = spanners[i];
if (spanner.state) continue; // дубликаты нам неинтересны
if (spanner.size1 < minSize) minSize = spanner.size1;
if (spanner.size2 > maxSize) maxSize = spanner.size2;
}
const bool result = Solution::allSizes.init(maxSize - minSize + 1, minSize);
if (! result) Serial.println(F("*** ошибка: недостаточно памяти для массива размеров"));
return result;
}
//
// Помечаем железобетонные ключи, чтобы исключить их их перебора
// заодно считаем количество разных размеров и заносим в Solution
//
bool markFerroconcretesAndFormSolution(uint8_t & totallyRemoved) {
Sizes<SSize> sizesArray;
if (! sizesArray.init(& Solution::allSizes)) {
Serial.println(F("*** ошибка: недостаточно памяти для массива размеров"));
return false;
}
for(uint8_t i = 0; i < totalSpanners; i++) {
Spanner * pSpanner = spanners + i;
if (pSpanner->state) continue; // дубликаты нам неинтересны
SSize & s1 = sizesArray[pSpanner->size1];
SSize & s2 = sizesArray[pSpanner->size2];
s1.counter++; s1.pSpanner = pSpanner;
s2.counter++; s2.pSpanner = pSpanner;
}
totallyRemoved = 0;
for (uint8_t i = 0; i < sizesArray.length; i++) {
SSize & curSize = sizesArray.item(i);
if (curSize.counter == 0) continue;
Solution::allSizes.totalSizes ++;
if (curSize.pSpanner->state) continue; // дубликаты нам неинтересны
if (curSize.counter == 1) {
currentSolution.pushSpanner(curSize.pSpanner);
curSize.pSpanner->state = Spanner::stateFerroconcrete;
totallyRemoved ++;
}
}
sizesArray.freeResources();
return true;
}
//
// Помечаем ключи, которые не нужны, когда уже есть
// "железобетонное" решение
//
uint8_t excludeHopelessSpanners(void) {
uint8_t totallyRemoved = 0;
for(uint8_t i = 0; i < totalSpanners; i++) {
Spanner & spanner = spanners[i];
if (spanner.state) continue; // дубликаты и железобетонные нам неинтересны
if (currentSolution.isNotApplicable(spanner)) {
spanner.state = Spanner::stateHopeless;
totallyRemoved ++;
}
}
return totallyRemoved;
}
//
// Перебор с ветвями и границами
//
void lookForSolution(const uint8_t startIndex = 0) {
for (uint8_t i = startIndex; i < totalSpanners; i++) {
const Spanner * const pSpanner = spanners + i;
if (currentSolution.isOverpriced(* pSpanner, bestSolution.total())) return;
if (currentSolution.isNotApplicable(* pSpanner)) continue;
currentSolution.pushSpanner(pSpanner);
if (currentSolution.isComplete()) {
bestSolution = currentSolution;
currentSolution.popSpanner();
return;
} else lookForSolution(i + 1);
currentSolution.popSpanner();
}
}
//
// Собственно, всё вместе.
//
void setup(void) {
Serial.begin(115200);
printSpanners(F("Ключи в самом начале"));
const uint8_t duplicates = removeDuplicates();
currentSolution.init(totalSpanners - duplicates);
bestSolution.init(totalSpanners - duplicates);
createSizesArray();
uint8_t ferroconcretes = 0;
markFerroconcretesAndFormSolution(ferroconcretes);
printSpanners(F("После поиска железного решения"));
printVar(Solution::allSizes.totalSizes);
Serial.println(currentSolution);
if (currentSolution.isComplete()) {
bestSolution = currentSolution;
} else {
const uint8_t hopeless = excludeHopelessSpanners();
printVar(hopeless);
printSpanners(F("После исключения безнадёжных"));
Spanner::sortByStates(spanners, totalSpanners);
totalSpanners -= (duplicates + hopeless + ferroconcretes);
printVar(totalSpanners);
printSpanners(F("После удаления всего"));
lookForSolution();
}
Serial.println(bestSolution);
}
void loop(void) {}
Сдублирую с небольшими изменениями (ещё немного дополнил):
Моё виденье каким должен быть алгоритм:
1. Фильтрация массива ключей. (типа сито)
2. Если фильтрация не справилась, то перебор сочетаниями без повторений.
Возможно, в процессе фильтрации можно разделить набор на поднаборы и уже к ним применять пункт 2.
Или при грамотно продуманной фильтрации перебор и не понадобиться.
Алгоритм:
1) Дубли с высокой ценой, при этом нужно учесть, что ключи могут быть перевёрнутыми, исключаются сразу.
2) Ключи, в которых размер встречается только один раз (уникальные ключи) или оставшиеся ключи после пункта 5 с этим же условием, по любому добавляются в набор.
3) Тут проявляется условие, что у каждого ключа есть два размера не равных между собой. Т.е. на примере набора из задания, который я и буду приводить дальше, 6-8, 9-11, 24-27 уникальные ключи с размерами 6, 11, 27, а размеры 8, 9, 24 уже не нужны для дальнейшей проверки.
4) При этом ключ 8-9 уже не нужен, т.к. его размеры уже включены в результирующий набор.
5) Далее могут присутствовать ключи, у которых один размер исключен, можно сравнить его стоимость с остальными ключами с таким же размером и если его цена больше то исключить его. При этом, если кол-во ключей с этим размером был равен 2, то более дешёвый ключ (это тот у которого оба размера раньше не были исключены) становиться уникальным. А его второй размер исключается из дальнейшего перебора.
6) Повторение в цикле пунктов 2-5 пока есть удаление ключей в этом цикле.
7) Если что-то останется, то перебор сочетаний без повторений.
Дополнительно:
1) Ссылки и указатели не использовал, т.к. не умею
2) От рекурсии отказался, т.к. она требует много памяти, решил что количество ключей важнее.
3) От размещения массива ключей (размеры и цена) в PROGMEM отказался, т.к. подумал, что это повлияет на скорость выполнения кода.
ЗЫ: много раз менял словесный алгоритм, теперь, вроде, полностью описал. А то был День трезвости, надо было отметить праздник. ))
Файл "TesBench.h" вместе с тремя файлами тестов кидал в папку с испытуемой программой.
В испытуемой программе делал три вещи
1. Вместо определения массива spanners вставлял #include "TestBehcn.h"
2. Перед засечением времени вставлял строку "setupTest();"
3. После отсечки времени, убирал печать результата, а вместо неё вставлял вызов "printTestRes(price, duration);"
(ну, с точностью до имён переменных - принцип такой).
Далее, в файле "TesBench.h" в десятой строке вставлял ник, а в восьмой имя файла с тестами и запускал. Она выполнял один тест, после чего сама ресетилась и выполняла следующий и так пока все не выполнит. Когда все тесты отработают, в мониторе аккуратная табличка.
Моя IDE не стирает монитор при перезаливке, так что ничего не трогая менял имя файла с тестами и запускал ещё раз. Результаты приписывались в хвост.
Когда все тесты для одного участника выполнены, тупо копировал их прямо в Excel (там не зря через табуляции всё выводится).
И так для всех участников.
Ну, а дальше в экселе обрабатывал.
P.S.
Ах, да, я там выше выкладывал только сами тесты. Если кто захочет "стенд" запускать, то полностью файлы тестов выглядят примерно так:
не знаю, стоит ли писать... интересно ли это кому-нибудь. Но хочется "излить душу"...
Ошибка в моей программе особенно обидна тем, что она не в алгоритме, а исключительно в синтаксисе.
Проявляется она в том, что если при начальной обработке набор не удалось упростить (то есть не выбрано и не выкинуто ни одного ключа) - процедура предварительной обработки ошибочно рапортует о найденном решении без ключей и с нулевой ценой :)
А еще это обидно потому, что, после исправления - именно на таких сложных наборах мой алгоритм мог бы сильнее всего опередить других участников.
вот, для примера посчитал несколько вариантов, на которых в тестах Евгения мой код спасовал:
Ключей
№ теста
Ник
Цена
Время
19
12
b707
19850
14984
EP
19850
41884
Kolyn
19850
845376
AndreyD
19850
3914480
19
18
b707
24531
16132
EP
24531
31568
Kolyn
24531
159364
AndreyD
24531
544052
19
28
b707
22262
15288
EP
22262
41772
Kolyn
22262
314760
AndreyD
22262
2020488
19
29
b707
24191
9764
EP
24191
35968
Kolyn
24191
291924
AndreyD
24191
1971952
19
31
b707
19484
12896
EP
19484
23512
Kolyn
19484
236240
AndreyD
19484
1930024
Это поучительная история о том, что в отладке программ никогда не бывает слишком много тестов. Приходится только удивляться, что мы втроем тусили тут в ветке почти месяц - и за это время никому не пришло в голову попробовать набор ключей, который не содержит дублей или очевидных кандидатов на вылет. и только когда Евгений сказал об ошибке - я нашел ее буквально за минуты...
Однако, результат есть результат.
Как говорят в футболе, "Красота игры забывается, в истории остается только счет" - кажется Лобановский
Приходится только удивляться, что мы втроем тусили тут в ветке почти месяц - и за это время никому не пришло в голову попробовать...
Увы, недоотлаженность - эта общая проблема всего современного программного обеспечения. При этом, что с этим делать - никто не знает. Формальные методы накладывают слишком серьезные (неприемлемые) ограничения на язык программирования, а все что остается вне этих рамок - во-первых, приводит к трудозатратам на отладку и тестирование порядка 80-90% от всей стоимости разработки, а во-вторых, совершенно не гарантируют результата.
Еще раз хотелось бы высказать свое восхищение Евгением Петровичем. Во-первых тем, что он, зная, какая предстоит работа, все-таки взялся за этот конкурс, и во-вторых, тем, как он его провел. Собственно, только после публикации результата большинству участников и наблюдателей стало понятно, что работа жюри по своему объему превышает работу всех участников конкурса вместе взятых.
Ну и еще одно наблюдение, имеющее, на мой взгляд, универсальный характер: уровень разработки современного ПО таков, что приходится выбирать только между "правильно, но крайне медленно" и "быстро, но неправильно".
Upd: Да, забыл, что еще, как правило, не обходится и без варианта "медленно и неправильно".
Как говорят в футболе, "Красота игры забывается, в истории остается только счет" - кажется Лобановский
Терпеть не могу эту фразу. Ещё (ровно из той же оперы) не люблю, когда нетренеры цитируют тренерское: "Игрок на площадке должен работать". Мне гораздо больше нравится, когда игроки играют, а не работают. Сравните немцев и бразильцев, чтобы понять о чём я. А Лобановский - он из тех же соображений и договорняки оправдывал: "Не понимаю, почему шахматисты могут согласиться на ничью, а нам нельзя". А о том, что я пришёл на трибуну игру смотреть, а не договорную тухлятину он как-то, видимо, не подумал.
Вообще, "победа любой ценой" - это неспортивно. По мне так Игорь Нетто, сказавший судье о неправильно забитом голе в матче чемпионата мира - великий Спортсмен, а Марадона с его "рукой Господа Бога" просто мячик пинал здорово, но Спортсменом не был.
Не ожидал! Я уже и за темой не следил, думал, что на последнем месте. По факту - моя программа медленнее всех, и если бы не ошибки других участников первого места мне бы не видеть.
Спасибо организатору конкурса за проделанную работу. Особенно впечатлил подход к подведению итогов .
Евгений Петрович, спасибо!
b707, прокомментируйте пожалуйста свой код, хотя бы кратко.
По мне так Игорь Нетто, сказавший судье о неправильно забитом голе в матче чемпионата мира - великий Спортсмен, а Марадона с его "рукой Господа Бога" просто мячик пинал здорово, но Спортсменом не был.
так всё таки рукой )))
пропустил, не являюсь болельшиком, по мне больше технические виды, спидвей к примеру...
PS эпизод этот помню...
// ver 4.6 01-12-20
// Remove sindex
// replace original sizes in spanners for 1,2,3...
// remove all non-needed variables in Size and Spanners
#define MAX_SIZE 160 // максимальный размер ключа в наборе
#define MAX_VARIANTS 8 // максимальное число ключей одного размера
#define NO_INDEX -1
//#define DEBUG
#ifdef DEBUG
#define DEBUG_PRINT(x) Serial.print(x)
#define DEBUG_PRINTF(x) Serial.print(F(x))
#define DEBUG_PRINTLN(x) Serial.println(x)
#define DEBUG_PRINTDEC(x) Serial.print(x, DEC)
#else
#define DEBUG_PRINT(x)
#define DEBUG_PRINTF(x)
#define DEBUG_PRINTDEC(x)
#define DEBUG_PRINTLN(x)
#endif
void memRep(uint8_t str, uint8_t level);
void print_span(uint8_t sp, uint8_t* sp_incl);
void print_spans(uint8_t* sp_incl);
#define GET_IND(x) (x & 0x7F)
#define GET_INCL(x) ((x & 0x80)>> 7)
#define SET_INCL(x) (x | 0x80)
/// Spanner class=================
struct Spanner {
long price;
uint8_t size1;
uint8_t size2;
//int8_t incl; // - not needed it
/**********/
Spanner(const uint8_t s1, const uint8_t s2, const long pr, const bool in = false) :
price(pr), size1(s1), size2(s2) {}
/**********/
uint8_t get_other_size_for_span(uint8_t fst_size) {
if (fst_size == size1) return size2;
return size1;
}
/**********/
long get_price() {
return price;
}
};
static Spanner spanners[] = {
{15, 10, 5550},
{14, 8, 7073},
{12, 11, 6750},
{8, 9, 2589},
{11, 15, 8976},
{16, 14, 8737},
{11, 8, 3149},
{12, 11, 3216},
{12, 13, 6443},
{14, 11, 6221},
{8, 10, 4561},
{9, 10, 5183},
{13, 12, 3173},
{16, 12, 8015},
{14, 15, 6196},
{13, 8, 3588},
{13, 10, 5004},
{14, 12, 2467},
{16, 8, 2922}
};
static constexpr uint8_t Span_q = sizeof(spanners) / sizeof(spanners[0]);
uint8_t* size_table;
/// Size class=================
/// Обьект Size
/// Size выбран если один из ключей с таким размером включен в итоговый набор
///
/// span_cnt - пока Size не выбран, span_cnt = числу ключей данного размера
/// если Size выбран, span_cnt = 0
///
/// spans - массив номеров ключей, отсортированный по цене от дешевых к дорогим
struct Size {
uint8_t span_cnt;
uint8_t spans[MAX_VARIANTS];
Size(const uint8_t sp) :
span_cnt(1) {
spans[0] = sp;
}
Size(const Size* ss) :
span_cnt(ss->span_cnt)
{
memcpy(spans, ss->spans, sizeof(spans));
}
/**********/
void add_span(const uint8_t sp, uint8_t* sp_incl) {
long price = spanners[GET_IND(sp_incl[sp])].price;
uint8_t s = 0;
while ((s < span_cnt) && (price > spanners[GET_IND(sp_incl[spans[s]])].price)) { s++; }
for (uint8_t ii = span_cnt; ii > s; ii--) spans[ii] = spans[ii - 1];
spans[s] = sp;
span_cnt++;
}
/**********/
int8_t replace_span(uint8_t new_sp, uint8_t old_sp) {
uint8_t sp_num = 0;
while (spans[sp_num] != old_sp && sp_num < span_cnt) { sp_num++; }
if (sp_num >= span_cnt) return -1;
spans[sp_num] = new_sp;
}
/**********/
int8_t del_span(uint8_t _sp, uint8_t* sp_incl) {
uint8_t ii = 0;
uint8_t sp_num = 0;
while (spans[sp_num] != _sp && sp_num < span_cnt) { sp_num++; }
if (sp_num >= span_cnt) return -1;
for (ii = sp_num + 1; ii < span_cnt; ii++) spans[ii - 1] = spans[ii];
span_cnt--;
return span_cnt;
}
/**********/
void select_size() {
span_cnt = 0;
}
/**********/
uint8_t lowest_price_span() {
return spans[0];
}
};
/// end of Size
// Global variables
Size** sizes;
uint8_t sizes_cnt = 0;
uint8_t* gen_incl;
uint8_t* best_incl;
/************************************************/
// включаем ключ в итоговый набор
/************************************************/
long select_span(const uint8_t _sp, uint8_t* span_incl, Size** s_cur) {
uint8_t sp = GET_IND(span_incl[_sp]);
#ifdef DEBUG
print_span(_sp, span_incl);
DEBUG_PRINT(" selected with price ");
DEBUG_PRINTLN(spanners[sp].price);
#endif
span_incl[_sp] = SET_INCL(span_incl[_sp]);
s_cur[spanners[sp].size1]->select_size();
s_cur[spanners[sp].size2]->select_size();
return spanners[sp].price;
}
/************************************************/
// упаковка рабочего списка ключей
// удаляем ключи, ранее помеченные как "лишние"
// передвигаем оставшиеся, чтобы в списке не было пустых мест
/************************************************/
void compact_incl(uint8_t* span_incl, Size** s_cur) {
uint8_t sp_cnt = span_incl[0];
while (span_incl[sp_cnt] == 255) sp_cnt--;
for (uint8_t ii = sp_cnt; ii > 0; ii--) {
if (span_incl[ii] == 255) {
if (ii == sp_cnt) { sp_cnt--; continue; }
if (GET_INCL(span_incl[sp_cnt]) == 0) {
uint8_t sp = GET_IND(span_incl[sp_cnt]);
Size* s1 = s_cur[spanners[sp].size1];
Size* s2 = s_cur[spanners[sp].size2];
s1->replace_span(ii, sp_cnt);
s2->replace_span(ii, sp_cnt);
}
span_incl[ii] = span_incl[sp_cnt];
sp_cnt--;
}
}
span_incl[0] = sp_cnt;
}
/************************************************/
// Первоначальная обработка списка ключей
//
// считаем число уникальных размеров,
// заменяем реальные размеры на их индексы
// удаляем дубли ключей с высокой ценой
/************************************************/
void count_sizes() {
uint8_t ii, jj = 0;
size_table = (uint8_t*)malloc(MAX_SIZE * sizeof(uint8_t));
int8_t* index = (int8_t*)malloc(MAX_SIZE * sizeof(int8_t));
uint8_t span_ind[Span_q] = { 0 };
uint16_t span_sizes[Span_q] = { 0 };
uint8_t sp_cnt = 0;
memset(index, NO_INDEX, MAX_SIZE);
for (ii = 0; ii < Span_q; ii++) {
uint8_t ss[] = { spanners[ii].size1, spanners[ii].size2 };
if (ss[1] < ss[0]) {
ss[1] = ss[0];
ss[0] = spanners[ii].size2;
}
uint16_t sp_size;
memcpy(&sp_size, ss, 2);
uint8_t prev_cnt = sizes_cnt;
for (jj = 0; jj < 2; jj++) {
if (index[ss[jj]] == NO_INDEX)
{
index[ss[jj]] = sizes_cnt;
size_table[sizes_cnt] = ss[jj];
sizes_cnt++;
}
}
if (prev_cnt == sizes_cnt) {
jj = 0;
while ((span_sizes[jj] != 0) && (span_sizes[jj] != sp_size)) { jj++; }
if (span_sizes[jj] == sp_size) {
if (spanners[ii].price < spanners[span_ind[jj]].price) {
span_ind[jj] = ii;
spanners[ii].size1 = index[ss[0]];
spanners[ii].size2 = index[ss[1]];
}
continue;
}
}
span_sizes[sp_cnt] = sp_size;
span_ind[sp_cnt] = ii;
sp_cnt++;
spanners[ii].size1 = index[ss[0]];
spanners[ii].size2 = index[ss[1]];
}
free(index);
realloc(size_table, sizes_cnt);
gen_incl = (uint8_t*)malloc((sp_cnt + 1) * sizeof(uint8_t));
best_incl = (uint8_t*)malloc((sp_cnt + 1) * sizeof(uint8_t));
memcpy(gen_incl + 1, span_ind, sp_cnt);
gen_incl[0] = sp_cnt;
#ifdef DEBUG
DEBUG_PRINTF("Span count = ");
DEBUG_PRINTLN(sp_cnt);
for (uint8_t k = 0; k < sp_cnt; k++) {
DEBUG_PRINTF("Span = ");
DEBUG_PRINTLN(span_ind[k]);
}
#endif
}
/************************************************/
// Создание таблицы обьектов Size
// представляем набор ключей как граф
// с вершинами Size и ключами = ребрами
/************************************************/
void create_sizes(uint8_t* sp_incl) {
uint8_t ii, jj;
uint8_t sp_cnt = sp_incl[0];
for (ii = 1; ii < sp_cnt + 1; ii++) {
uint8_t sp = GET_IND(sp_incl[ii]);
// ключи с нулевой ценой включаем в итоговый набор автоматически
if (0 == spanners[sp].get_price()) sp_incl[ii] = SET_INCL(sp_incl[ii]);
uint8_t ss[] = { spanners[sp].size1, spanners[sp].size2 };
for (jj = 0; jj < 2; jj++) {
uint8_t s = ss[jj];
if (sizes[s] == 0)
{
sizes[s] = new Size(ii);
}
else {
sizes[s]->add_span(ii, sp_incl);
}
}
}
DEBUG_PRINTF("Add sizes ");
DEBUG_PRINTLN(sizes_cnt);
}
/************************************************/
// Предварительная сортировка графа
/************************************************/
long begin_sort(uint8_t* span_incl, Size** s_cur, long cur_min, long cur_result) {
bool changed = true;
DEBUG_PRINT(" enter to begin sort ");
DEBUG_PRINTLN(cur_result);
// цикл ниже повторяется до тех пор, пока граф не перестанет меняться
while (changed) {
uint8_t sp_cnt = span_incl[0];
for (uint8_t ii = 0; ii < sizes_cnt; ii++) {
Size* s1 = s_cur[ii];
// включаем "очевидные" ключи - размер которых представлен в одном экземпляре
if (s1->span_cnt == 1) {
cur_result += select_span(s1->spans[0], span_incl, s_cur);
}
}
// если в какой-то момент цена набранных ключей превысила текущий минимум -
// останавливаемся, выходим из процедуры и вообще прекращаем поиск в этой итерации
if (cur_result > cur_min) { DEBUG_PRINTLN(" over value ");return cur_result; }
changed = false;
for (uint8_t ii = 1; ii <= sp_cnt; ii++) {
if (GET_INCL(span_incl[ii]) == 0) {
#ifdef DEBUG
print_span(ii, span_incl);
DEBUG_PRINT(" incl ");
DEBUG_PRINTLN(span_incl[ii]);
#endif
uint8_t sp = GET_IND(span_incl[ii]);
Size* s1 = s_cur[spanners[sp].size1];
Size* s2 = s_cur[spanners[sp].size2];
// ключи, оба размера которых уже "выбраны"
// помечаем к удалению
if ((s1->span_cnt == 0) && (s2->span_cnt == 0)) {
changed = true;
span_incl[ii] = 255;
DEBUG_PRINTLN(" -> del ");
}
// ключи, один размер у которых выбран, а другой нет
else if ((s1->span_cnt == 0) != (s2->span_cnt == 0)) {
Size* s_w = s1;
if (s1->span_cnt == 0) s_w = s2;
DEBUG_PRINT(" ii ");
DEBUG_PRINT(ii);
DEBUG_PRINT(" incl ");
DEBUG_PRINTLN(span_incl[ii]);
// можно удалить, если это не самый дешевый ключ своего размера
if (ii != s_w->lowest_price_span()) {
if (s_w->del_span(ii, span_incl) >= 0) {
DEBUG_PRINT(" ii -> del ");
DEBUG_PRINT(ii);
changed = true; span_incl[ii] = 255;
}
}
}
}
}
// если что-то изменилось - упаковываем ключи заново
if (changed) {
DEBUG_PRINTLN(" compacting ");
compact_incl(span_incl, s_cur);
}
}
DEBUG_PRINTLN(" exit begin sort ");
return cur_result;
}
/************************************************/
// проверка, является ли текущий набор полным
// если хоть у одного Size число ключей не равно нулю - значит не полный
// вот тут была ошибка, стоившая мне победы :)
/************************************************/
long test_solution(Size** s_cur) {
uint8_t ii;
long jj = 0;
int8_t factor = 1;
for (ii = 0; ii < sizes_cnt; ii++) {
if (s_cur[ii]->span_cnt != 0) {
factor = -1;
break;
}
}
return (factor);
}
/************************************************/
// копирует список ключей заданной вершины
/************************************************/
uint8_t select_edges2(uint8_t* edges, Size** s_cur, uint8_t st) {
uint8_t ii, jj = 0;
Size* s1 = s_cur[st];
if (s1->span_cnt > 1) {
for (ii = 0; ii < s1->span_cnt; ii++) { edges[ii] = s1->spans[ii]; }
return s1->span_cnt;
}
return 0;
}
/************************************************/
// поиск "центра" графа методом "срывания листьев"
/************************************************/
uint8_t find_center(uint8_t* span_incl, Size** s_cur) {
uint8_t changed = 1;
uint8_t sp_cnt = span_incl[0];
// ищем ключи, одна сторона которых выбрана, а другая нет
// считаем что такие ключи лежат на краях графа (это "листья")
// удаляем их
//
// повторяем, пока граф не перестанет меняться
while (changed) {
changed = 0;
uint8_t changed_sizes[16] = { 0 };
for (uint8_t ii = 1; ii < sp_cnt + 1; ii++) {
if (GET_INCL(span_incl[ii]) == 0) {
uint8_t sp = GET_IND(span_incl[ii]);
Size* s1 = s_cur[spanners[sp].size1];
Size* s2 = s_cur[spanners[sp].size2];
Size* s_w = s1;
uint8_t sz = spanners[sp].size1;
if ((s1->span_cnt == 0) != (s2->span_cnt == 0)) {
if (s1->span_cnt == 0) {
s_w = s2;
sz = spanners[sp].size2;
}
uint8_t del_result = s_w->del_span(ii, span_incl);
if (del_result > 0) {
changed_sizes[changed++] = sz;
span_incl[ii] = 255;
}
else if (del_result == 0) {
DEBUG_PRINTF("Center size for iteration = ");
DEBUG_PRINTLN(sz);
return sz;
}
}
}
}
for (uint8_t ii = 0; ii < changed; ii++) {
Size* s1 = s_cur[changed_sizes[ii]];
s1->span_cnt = 0;
}
}
for (uint8_t ii = 1; ii < sp_cnt + 1; ii++) {
if (GET_INCL(span_incl[ii]) == 0) {
uint8_t sp = GET_IND(span_incl[ii]);
DEBUG_PRINTF("Res size for iteration = ");
DEBUG_PRINTLN(spanners[sp].size1);
return spanners[sp].size1;
}
}
}
/************************************************/
// очистка временной таблицы размеров
// удаляем обьекты в обратном порядке
// по отношению к порядку из создания
// для уменьшения фрагментации
/************************************************/
void del_sizes2(Size** dest, Size** source) {
uint8_t ii, jj;
for (jj = sizes_cnt; jj > 0; jj--) {
ii = jj - 1;
if ((dest[ii] != 0) && (dest[ii] != source[ii])) delete dest[ii];
}
}
/************************************************/
// инициализация таблицы размеров
/************************************************/
void init_sizes(Size** dest) {
uint8_t ii;
for (ii = 0; ii < sizes_cnt; ii++) { dest[ii] = 0; }
}
/************************************************/
// "умное" копирование таблицы размеров для
// очередного шага рекурсии
//
// размеры, которые не будут меняться - передаем ссылкой
// остальным создаем копии
/************************************************/
void cp_sizes3(Size** dest, Size** source) {
uint8_t ii;
for (ii = 0; ii < sizes_cnt; ii++) {
if (source[ii]->span_cnt != 0) {
if (dest[ii] != 0) *(dest[ii]) = *(source[ii]);
else dest[ii] = new Size(source[ii]);
}
else {
if ((dest[ii] != 0) && (dest[ii] != source[ii])) delete dest[ii];
dest[ii] = source[ii];
}
}
}
/************************************************/
// основная процедура оптимизации
/************************************************/
long minimum_find(uint8_t* span_incl, Size** s_cur, long cur_min, long cur_result, uint8_t level) {
// первым делом проверяем, есть ли что оптимизировать
// или у нас уже полный набор
level++;
DEBUG_PRINTF("==== enter to new level ");
DEBUG_PRINTLN(level);
bool min_found = false;
int8_t res = test_solution(s_cur);
if (res > 0) {
DEBUG_PRINTF("New solution found! min = ");
DEBUG_PRINTLN(cur_result);
// если найден новый минимум - запоминаем набор ключей
if (cur_result < cur_min) {
memcpy(best_incl, span_incl, (span_incl[0] + 1) * sizeof(uint8_t));
cur_result = cur_result * -1;
}
return cur_result;
}
// если набор пока не полный
else {
DEBUG_PRINTF("non-full set val = ");
DEBUG_PRINTLN(cur_result);
// если текущий набор уже дороже ранее найденного минимума - выходим
if (cur_result >= cur_min) return cur_result;
DEBUG_PRINTLN("go next");
// создаем массивы для временных копий
// массива размеров и массива ключей
uint8_t k = 0;
uint8_t* new_s_incl = (uint8_t*)malloc((span_incl[0] + 1) * sizeof(uint8_t));
if (!new_s_incl) memRep(1, level);
Size** new_sizes = (Size**)malloc(sizes_cnt * sizeof(Size*));
if (!new_sizes) memRep(3, level);
// копируем данные во временные массивы
init_sizes(new_sizes);
uint8_t new_edges[MAX_VARIANTS] = { 0 };
// select start size
memcpy(new_s_incl, span_incl, (span_incl[0] + 1) * sizeof(int8_t));
cp_sizes3(new_sizes, s_cur);
// ищем вершину в "центре" оставшейся части графа
// и создаем список прилегающих к ней ребер (ключей)
uint8_t start_size = find_center(new_s_incl, new_sizes);
uint8_t var_cnt = select_edges2(new_edges, s_cur, start_size);
// последовательно выбираем по одному ключю из списка
// начиная с самых дешевых
// и считаем весь оставшийся граф
for (k = 0; k < var_cnt; k++) {
// снова копируем размеры и ключи, потому что предыдущая итерация мх "испортила"
memcpy(new_s_incl, span_incl, (span_incl[0] + 1) * sizeof(int8_t));
cp_sizes3(new_sizes, s_cur);
// выбираем очередной ключ
long temp_result = cur_result + select_span(new_edges[k], new_s_incl, new_sizes);
if (temp_result >= cur_min) break;
DEBUG_PRINTF("==== new edge at level ");
DEBUG_PRINTLN(level);
// и прогоняем всю оптимизацию с начала
temp_result = begin_sort(new_s_incl, new_sizes, cur_min, temp_result);
if (temp_result > cur_min) continue;
temp_result = minimum_find(new_s_incl, new_sizes, cur_min, temp_result, level);
if ((temp_result < 0) && ((temp_result * -1) < cur_min)) {
cur_min = temp_result * -1;
min_found = true;
}
}
if (min_found) {
cur_result = cur_min * -1;
}
del_sizes2(new_sizes, s_cur);
free(new_sizes);
free(new_s_incl);
return cur_result;
}
}
/************************************************/
// отладочный вывод параметров ключа
void print_span(uint8_t _sp, uint8_t* sp_incl) {
uint8_t sp = GET_IND(sp_incl[_sp]);
uint8_t s1 = size_table[spanners[sp].size1];
uint8_t s2 = size_table[spanners[sp].size2];
DEBUG_PRINTF("Span ");
DEBUG_PRINT(_sp);
DEBUG_PRINTF(" ");
DEBUG_PRINT(s1);
DEBUG_PRINTF("x");
DEBUG_PRINT(s2);
}
/************************************************/
// печать найденного минимума
/************************************************/
void print_spans(uint8_t* sp_incl) {
uint8_t ii;
long jj = 0;
uint8_t sp_cnt = sp_incl[0];
//Serial.print(F("Spans in set ="));
//Serial.print(sp_cnt);
Serial.println(F("=== Span selection ==="));
for (ii = 1; ii < sp_cnt + 1; ii++) {
if (GET_INCL(sp_incl[ii]) > 0) {
uint8_t sp = GET_IND(sp_incl[ii]);
uint8_t s1 = size_table[spanners[sp].size1];
uint8_t s2 = size_table[spanners[sp].size2];
jj += spanners[sp].price;
Serial.print("Span ");
Serial.print(s1);
Serial.print("x");
Serial.println(s2);
}
}
Serial.print(F("Total value = "));
Serial.println(jj / 100.0, 2);
}
/************************************************/
// отладочное сообщение о проблемах с памятью
/************************************************/
void memRep(uint8_t str, uint8_t level) {
Serial.print(F("Not enough memory at level "));
Serial.println(level);
switch (str) {
case 1: Serial.println(F("new_incl"));break;
case 3: Serial.println(F("sizes"));break;
}
while (1);
}
/************************************************/
void setup() {
uint8_t ii;
long minimum = 0;
long cur_res = 0;
Serial.begin(115200);
while (!Serial) {};
uint32_t old_mil = micros();
// Первоначальная обработка списка ключей
count_sizes();
sizes = (Size**)malloc(sizeof(Size*) * sizes_cnt);
init_sizes(sizes);
// Создание таблицы обьектов Size
// представляем набор ключей как граф
// с вершинами Size и ключами = ребрами
create_sizes(gen_incl);
uint8_t sp_cnt = gen_incl[0];
// считаем полную стоимость всех оставшихся ключей
// для использования как начальной оценки "минимума"
for (ii = 1; ii < sp_cnt + 1; ii++) { uint8_t sp = GET_IND(gen_incl[ii]); minimum += spanners[sp].price; }
// Предварительная сортировка графа
cur_res = begin_sort(gen_incl, sizes, minimum, cur_res);
// основная процедура оптимизации
minimum = minimum_find(gen_incl, sizes, minimum, cur_res, 0);
uint32_t tim = micros() - old_mil;
print_spans(best_incl);
Serial.print(F("Calculation time = "));
Serial.print(tim / 1000.0, 3);
Serial.println(" ms");
}
void loop() {}
При оптимизации наборов ключей я решил трактовать их как граф, где вершинами являются размеры, а ребрами - сами ключи. Причем основная оптимизация делается через работу с вершинами, поэтому первым делом я считаю число уникальных размеров в наборе и создаю табличку вершин, для каждой из которых храню число ребер и их список. Признаком того, что вершина выбрана - является равенство нулю числа ребер на вершине.
Для ключей тоже создаем временную табличку, где храним индекс ключа и флаг, выбран ключ или нет. Первоначально я - по примеру других участников - хранил и признак того, что ключ исключен из набора. Но потом пришел к тому, что исключенные ключи эффективнее просто стирать из таблицы , что уменьшает необходимую память и увеличивает скорость работы.
На этапе создания таблиц так же избавляемся от дублей, оставляя только самые дешевые ключи для заданных комбинаций.
Первоначальная обработка (А) использует те же подходы, что и в алгоритме Андрея.
A.1 - выбираем ключи, размер которых представлен в единственном экземпляре
А.2 - исключаем ключи, лежащие между двумя уже выбранными вершинами
А.3 - находим ключи, один конец которых выбран, а другой нет. Если этот ключ не является самым дешевым для данного размера - его можно выкинуть, более дешевый ключ точно будет выгоднее
Шаги А.1 - А.3 повторяем в цикле до тех пор, пока граф не перестанет меняться. После этого аналитическая часть закончена, переходим к итерациям.
Для начала проверяем, не является ли набор полным, и если да - новым минимумом. Если так - запоминаем найденный набор и выходим из этой итерации.
Алгоритм дальнейшего поиска таков.
(шаг В) Выбираем любую из еще не включенных в набор вершин. Очевидно, что у любой такой вершины как минимум 2 ребра, иначе она была бы уже выбрана. И так же очевидно, что как минимум одно из этих ребер должно войти в оптимальный набор. Все что нам нужно - это посчитать наборы для каждого из этих ребер и сравнить их между собой. Фактически можно сказать. что вся задача упрощается до сравнения двух-трех вариантов ;)...
(шаг С) выбираем любое из ребер данной вершины, считаем его включенным в конечный набор и решаем для полученного набора (который стал чуть меньше) исходную задачу - то есть сначала выполняем шаги А, потом шаг Б и шаг С рекурсивно все глубже и глубже, пока не будет набран полный набор.
Далее поднимаемся обратно до текущего уровня шага С, выбираем следующее ребро и ищем минимум для него. Минимальный из этих наборов и будет глобальным минимумом.
Тут есть некоторые уточнения, которые помогают сделать поиск эффективнее. На шаге В выгоднее выбирать не любую вершину, а такую, что лежит максимально удаленно от концов графа - тогда при выборе ребра, прилежащего к вершине - на стадии А есть шанс больше упростить граф, чем если вершина крайняя. Для этого используется функция find_center()
И второе - ребра лучше тестировать в порядке возрастания стоимости, тогда есть шанс найти минимальное решение как можно раньше и все остальные ветки просто отсеять, когда, в ходе итерации, их текущая цена превысит найденный минимум.
А.3 - находим ключи, один конец которых выбран, а другой нет. Если этот ключ не является самым дешевым для данного размера - его можно выкинуть, более дешевый ключ точно будет выгоднее
До этого сам не дошел, а когда Андрей написал п.5:) посчитал не совсем честным включать в свой алгоритм.
До этого сам не дошел, а когда Андрей написал п.5:) посчитал не совсем честным включать в свой алгоритм.
Время работы моей программы с первого результата (выложенного впервые в #166) почти не изменилось - на наборе ЕП тогда было 2.8мс, а в текущей версии - 2.53мс. Это потому что основной алгоритм сложился сразу и в дальнейшем я его почти не менял, потратив две недели на борьбу с нехватками памяти.
Насколько я вижу в обсуждении, первый раз о своем "пункте 5" Андрей заговорил лишь спустя 10 дней. Так что мы с ним пришли к этой хитрости независимо.
До этого сам не дошел, а когда Андрей написал п.5:) посчитал не совсем честным включать в свой алгоритм.
Время работы моей программы с первого результата (выложенного впервые в #166) почти не изменилось - на наборе ЕП тогда было 2.8мс, а в текущей версии - 2.53мс. Это потому что основной алгоритм сложился сразу и в дальнейшем я его почти не менял, потратив две недели на борьбу с нехватками памяти.
Насколько я вижу в обсуждении, первый раз о своем "пункте 5" Андрей заговорил лишь спустя 10 дней. Так что мы с ним пришли к этой хитрости независимо.
Это я только о своем коде. Не подумайте плохого:))
Насколько я вижу в обсуждении, первый раз о своем "пункте 5" Андрей заговорил лишь спустя 10 дней. Так что мы с ним пришли к этой хитрости независимо.
Да, чужой код анализировать это ещё то удовольствие. ) Ещё и когда мало знаешь язык программирования. Да и принципиально хотел разработать своё решение отличное от других, а не пытался разобраться в других кодах.
Коллеги, получил координаты двух участников, а от b707 ничего нет. Напишите. пожалуйста, чтобы мне на почту один раз ходить, а не несколько.
----------------------------------
Обещанные пояснения к моей программе.
Итак, как уже говорилось - использовались только общие методы, применимые практически для любой переборной задачи, а именно: сначала пытаемся сократить количество перебираемых элементов, удаляя очевидно ненужные, а затем перебираем оставшиеся, используя метод ветвей и границ. Сокращение элементов для перебора делается в три шага.
Итак, первое - удаляем дубликаты. На самом деле для данной задачи это скорее вредно, чем полезно, т.к. вряд ли стоит ожидать, что магазин выложит несколько одинаковых ключей в одном лоте, поэтому это скорее потеря времени, чем его экономия. Но, сказано же - общие методы - делаем. Для удаления дубликатов в строке №143 вызывается функция removeDuplicates(), сама функция находится в строках №№40-49. Первое, что в ней делается - сортировка (стр. №41) по следующим критериям: по первому размеру, если равно, то по второму размеру, если и здесь равенство, то по цене. Всё в порядке возрастания. Сама по себе сортировка живёт в файле Spanner.h и она совершенно стандартная с использованием стандартной функции qsort, поэтому я на ней не останавливаюсь. В результате сортировки все одинаковые ключи окажутся рядом, причём первым будет идти самый дешёвый. Поэтому, далее мы проходим по получившемуся массиву ключей и помечаем как Spanner::stateDuplicate все элементы, которые не уникальны, кроме первого из группы. Это сделано в строках №№43-46. Функция возвращает количество помеченных ключей.
Далее в строках №№144-146 выполняются некие служебные действия, а именно инициализируются две структуры "решения". Одна для "текущего", а другая для "наилучшего". Им передаётся количество уникальных ключей (которая является верхней оценкой количества ключей в решении, больше не будет). И ( в строке №146) создаётся массив всех имеющихся размеров. Всё это нам пригодятся в будущем.
Второе - находим точно ("железобетонно") входящие в решение ключи и формируем начальное решение из этих ключей. Под "жеоезобетонными" мы понимает те ключи, у которых хотя бы один из размеров больше нигде не встречается. Такой ключ обязан быть в решении, иначе в нём не будет этого размера.
Для этого в строке №148 вызываем функцию markFerroconcretesAndFormSolution (сама функция находится в строках №№70-100). Функция возвращает true, если всё в порядке и false, если не хватило памяти. Как видите я забыл после неё проверить, что она вернула, торопился. Также функция помещает в свой первый параметр (по ссылке) количество таких "железобетонных ключей", которые она нашла. Давайте на неё смотреть.
Для начала она запрашивает массив размеров на максимально возможно количество (строки №№71-75), затем проходит по всему массиву ключей и считает сколько и каких размеров там присутствует. Для этого она для каждого из размеров ключа во-первых, увеличивает счётчик, а во-вторых запоминает в каком именно ключе такой размер встретился (строки №№77-84). Таким образом, после строки №84 в массиве sizesArray мы для каждого размера имеем количество (сколько раз он у нас встречается) и адрес последнего из найденных ключей в котором такой размер встречался.
Ну, осталось только пометить как "железобетонные" те ключи, у которых хотя бы один размер встретился только один раз и добавить их в "базовое решение". Это делается в строках №№87-97. Попутно считается общее количество имеющихся размеров (строки №№ 86 и 90) - оно нам пригодится для оценки полноты найденных решений. Обратите внимание на крайне неудачный комментарий в строке №91 (только сейчас заметил, но поправить уже не могу - ответили). Этот комментарий скрывает очень важный момент. Там ведь проверяется не то, что это дубликат, а то, что ключ не имеет никакого "ненулевого" состояния. Это важно! Если бы мы здесь проверяли только на "дубликат", то ключи у которых оба размера уникальны и больше нигде не встречаются, попадали бы в базовое решение дважды (были бы "дважды железобетонными"), но поскольку мы проверяем на "ненулевое состояние", ключ, который уже помечен как "железобетонный" здесь будет отсеян и второй раз в решение не попадёт.
После завершения формирования базового решения (содержащего только железобетонные ключи), стоит проверить, а не является ли оно полным, т.е. не содержит ли всех размеров? Если так, то нам сильно повезло и ничего больше делать не нужно - оптимальное решение найдено. Такая проверка делается в строке №152. Если же нет, продолжаем работу - строки №№155-162
Наконец, третье - теперь, когда у нас уже есть "базовое решение", содержащее только железобетонные ключи, мы можем проверить все оставшиеся ключи на предмет: а не присутствую ли оба размера данного ключа в базовом решении. Если присутствуют, то такой ключ нам неинтересен - он никак не может попасть в оптимальное решение и мы будем помечать его как бесперспективный. Это делается вызовом функции excludeHopelessSpanners() (строка №155), которая делает всё, о чём говорилось и возвращает количество ключей помеченных как бесперспективные. Сама функция в строках №№106-117 и она достаточно прозрачна и очевидна.
Таким образом, после строки №155 у нас имеется базовое решение, содержащее железобетонные ключи, а все ключи в наборе помечены либо как дубликаты, либо как железобетонные, либо как бесперспективные, либо не помечены никак. Вот именно последние, никак не помеченные, и будут участвовать в переборе. Здесь, для удобства мы ещё раз пересортируем ключи в наборе таким образом, чтобы никак не помеченные ключи оказались в самом начале и при этом были отсортированы по возрастанию цены (это очень важно для применения метода ветвей и границ). Вызов сортировки в строке №158. Сама по себе сортировка, опять же, достаточно проста и очевидна, мы на неё не будем останавливаться. И, наконец, в строке №59, мы запишем в переменную totalSpanners количество оставшихся (никак не помеченных) ключей.
Всё! Мы готовы к перебору. Перебирать будем totalSpanners первых элементов массива spanners.
Перебором занимается функция lookForSolution (строки №№ 122 - 135). Она выполняет перебор методом ветвей и границ, который я подробно описывал в посте #228, поэтому здесь описывать не буду.
На закуску несколько замечаний о сортировке:
1.
при работе со структурированными данными, сортировка - наше всё! Мы применили её дважды, но если бы массивы ключей были большими, то стоило бы применить ещё пару раз, чтобы избавиться от проверок в строках №№ 79, 91 и 110. Т.е. сортировка - главный инструмент в задачах со структурированными данными - она облегчает и ускоряет всё. Да собственно и по жизни, вот прикиньте (особенно в момент, когда чистите снег во дворе или прибираетесь в мастерской), сколько времени своей жизни мы посвящаем перекладыванию предметов с места на место!
2.
Я использую функцию qsort - это хорошая быстрая сортировка, но для маленьких массивов она вполне может проигрывать обычному "пузырьку". Если бы стояла цель выжать всё возможное быстродействие, стоило бы сравнить эти два метода и выбрать лучший для данного случая.
3.
Обычно в задачах, где данных много или они сами по себе большие, никто и никогда не сортирует сам массив данных, как я это делал здесь. Вместо этого заводят массив указателей (на ключи) и сортируют его. Это просто необходимо, когда, например, нам нужно иметь наши данные одновременно отсортированными по разным критериям, или когда наши данные сидят в read-olny памяти и мы не можем их перемещать. Просто заводим столько массивов указателей, сколько критериев, и сортируем эти массивы, а сами данные не трогаем вовсе.
Коллеги, получил координаты двух участников, а от b707 ничего нет.
написал
и некоторые замечания по сортировке
ЕвгенийП пишет:
удаляем дубликаты.
Первое, что в ней делается - сортировка (стр. №41) по следующим критериям: по первому размеру, если равно, то по второму размеру, если и здесь равенство, то по цене. Всё в порядке возрастания.
сортировка всех ключей сначала по одному размеру, потом по второму... мне показался такой подход переусложненным. Я преобразую размеры каждого ключа в целое uint16_t, где старший байт это меньший размер, а младший - больший. И потом сравниваю полученное целое с аналогичным индексом для других ключей простым перебором, сразу же выкидывая дубликаты. Возможно, сортировка массива индексов дала бы небольшое ускорение, но эту процедуру надо выполнить всего один раз, да и массивы у нас по определению не больше нескольких десятков ключей...
что касается основного поиска минимума, вы пишете:
Цитата:
Вот именно последние, никак не помеченные, и будут участвовать в переборе. Здесь, для удобства мы ещё раз пересортируем ключи в наборе таким образом, чтобы никак не помеченные ключи оказались в самом начале и при этом были отсортированы по возрастанию цены (это очень важно для применения метода ветвей и границ).
Хочу сказать о выделенной фразе. Я в своей программе сознательно отказался от сквозной сортировки всего массива. Как мне кажется... это бессмысленная трата времени. Какой смысл в том, что ключ 12х14 стоит дешевле, чем 22х24 ? - это же разные ключи, они не заменяют друг друга...
Сортировка имеет смысл только среди ключей одного размера. В случае же общих индексов я сначала делал их упорядоченными, а потом от этого отказался и выиграл во времени. Затраты на поддержания порядка в сортированном индексе съедают больше ресурсов, чем дают преимуществ. Вставка элемента в упорядоченный индекс требует поиска верной позиции для вставки и сдвига остальных элементов "ниже". В не сортированный - вы просто добавляете элемент в конец. Тоже самое с удалением - после удаления элемента из середины списка вам придется сдвинуть все остальные на позицию вперед, в несортированном же вы просто перемещаете последний элемент на место удалененого. В среднем любые операции с несортированным списком в N/2 раз быстрее, чем с сортированным. ... за исключением поиска конкретного элемента. Но как раз поиск конкретного ключа в общем индексе нам вообще не требуется...
... после удаления элемента из середины списка вам придется сдвинуть все остальные на позицию вперед, в несортированном же вы просто перемещаете последний элемент на место удалененого. В среднем любые операции с несортированным списком в N/2 раз быстрее, чем с сортированным. ... за исключением поиска конкретного элемента. Но как раз поиск конкретного ключа в общем индексе нам вообще не требуется...
А ведь и правда, а я при удалении элемента в несортированном массиве сдвигал все последующие друг за другом до конца массива.
ЕвгенийП пишет:
Коллеги, получил координаты двух участников, а от b707 ничего нет. Напишите. пожалуйста, чтобы мне на почту один раз ходить, а не несколько.
Просьба ещё сообщить номер для отслеживания, а то у меня почтальоны ленивые, могут в течении месяца только сообщить, что мене что-то пришло.
Какой смысл в том, что ключ 12х14 стоит дешевле, чем 22х24 ? - это же разные ключи, они не заменяют друг друга...
Значит Вы не поняли основную идею метода ветвей и границ. Смысл сортировки по цене именно в том, что если какой-то ключ вылез за текущую минимальную цену, то остальные можно не перебирать. Если они не были отсортированы - это бы не работало. Прочитайте ещё раз описание метода, если не поможет, скажите, я сделаю новое с примерами и картинками.
b707 пишет:
Вставка элемента в упорядоченный индекс требует поиска верной позиции для вставки и сдвига остальных элементов "ниже". В не сортированный - вы просто добавляете элемент в конец.
Ну, у нас просто методы разные. При переборе мне не нужно поддерживать порядок в массиве по той простой причине, что я его не нарушаю - я не трогаю массив вообще.
Значит Вы не поняли основную идею метода ветвей и границ. Смысл сортировки по цене именно в том, что если какой-то ключ вылез за текущую минимальную цену, то остальные можно не перебирать.
а, ну да... тогда согласен. Идея полного перебора, без предварительного учета размеров, как-то никак не укладывается у меня в голове. Я ее вообще не принимаю в расчет :) Поэтому и вопросы такие глупые...
Рассказал троим моим знакомым (друзьям, родственникам) о конкурсе, один точно подтвердил, что будет участвовать в будущих конкурсах (контроллеры у него уже есть), два других пока не знаю будут ли, у них ардуиновских наборов нет, но я себе заказал nano every Atmega4808 две штуки (в январе должны прийти), в замен моих находящихся в работе Nano v3.0, так что если что смогу им отдать нанки под конкурс, плюс ещё что потребуется для будущего конкурса из моих запасов, ну если они захотят участвовать.
Дополнение:
Чёт я поторопился заказать Nano Every Atmega4808, пишут фигня ещё та, отменил заказ. Заказал Arduino Nano Every ATMega4809, но одну штуку на пробу.
Очень сомневаюсь в реальности. Большие дяди меряются заковыристыми алгоритмами. А такие алгоритмы демонстрируют свои преимущества при больших наборах данных, что для ардуины проблемно. На малых же наборах они даже перебору данных проигрывают. Глянувши на этот конкурс, видно что более менее продуманный алгоритм у b707 сделан, и он бы порвал 'переборщиков' всех мастей, будь наборы данных побольше и стека ардуины поглубже. А в целом - печалька, даже слона по частям съесть не сумели. Не смотря на ваши мольбы и просьбы )))
Давно бредится в голове идея игры (отдалённо типа "Core War"), даже пробовал пару раз. Но не получается интересной. Либо слишком сложная, либо слишком простая (с ничейной смертью).
Форум то тематический. Очень трудно придумать задачи, годные для выполнения на МК и не требующие дополнительных модулей, блоков и т.д. И в то же время и про программную составляющую не забыть, сделав ее достаточной сложности.
Можно придумать задачу на принципы работы со входящими и исходящими сигналами, без внешних источников и приёмников, т.е с имитацией. Программно на ардуино это осуществимо.
ПС: чёт у меня "очучение", что у меня часовой пояс ближе к Москве, чем у остальных.
Можно придумать задачу на принципы работы со входящими и исходящими сигналами, без внешних источников, т.е с их имитацией. Программно на ардуино это осуществимо.
Честно говоря, даже предложенная Петровичем задача вызвала у меня, минимум, одну отрицательную эмоцию: задачи подобного типа должны брать входные данные из файла, а не из включенного в код массива. Так что "портирование" данной задачи на Ардуино не обошлось без явных потерь.
А в этой связи родилась мысль: а так ли уж необходимо, чтобы задачи на программирование были непосредственно адаптированы к Ардуино? Может, интерес вызовет и задачи, ориентированные на ПК?
Ну и по поводу Ардуино: здесь, безусловно, интерес представляют не только (а, возможно, и не столько) задачи на программирование, но и задачи на умелое использование аппаратных ресурсов контроллера. И ограничение "без внешних цепей" тоже представляется чрезмерно жестким: вряд ли требование наличия самой стандартной периферии: нескольких кнопок, светодиодов, потенциометров или дисплея 1602 может вызвать трудности для обитателей данного форума.
Для меня необходимость выполнить задачу на заданной платформе составила главную изюминку конкурса. Это форум ардуино и потому я за эадачки для Мк, а не для пк
Ну и по поводу Ардуино: здесь, безусловно, интерес представляют не только (а, возможно, и не столько) задачи на программирование, но и задачи на умелое использование аппаратных ресурсов контроллера. И ограничение "без внешних цепей" тоже представляется чрезмерно жестким: вряд ли требование наличия самой стандартной периферии: нескольких кнопок, светодиодов, потенциометров или дисплея 1602 может вызвать трудности для обитателей данного форума.
Тоже согласен, у каждого ардунщика должен быть минимальный набор, это тоже залог для будущих конкурсов.
Ну, изюминка здесь исключительно в ограничении на объем RAM. В принципе, тоже важно. Вопрос: будет ли экономное использование памяти учитываться при оценке.
Ну чё там, чё с результатом конкурса? Я уже почти согласен приз не получать, если выиграю, главное что там по результату.
Ну или вариант кода ЕвгенийП увидеть.
Итак, коллеги, ещё раз прошу извинения за задержку, но таки приступаем к подведению итогов.
Тема сильно замусорена, потому я боюсь, что-то пропустить или взять не последние версии. Тем более, заявления типа
Пусть предыдущий код будет на конкурс, а этот на дальнейшее обсуждение.
тоже однозначности не добавляют.
Поэтому, пока я тут готовлю тесты и мучаю коды, проверьте. пожалуйста, правильные ли версии я взял.
Сейчас я работаю с такими версиями:
AndreyD - из поста #289
kolyn - из поста #288
b707 - из поста #274.
Если я кого-то пропустил (был ещё участник) или у кого-то взял не последнюю версию, пожалуйста скажите!
у меня была одна версия, эта правильная
Предварительные итоги.
Коллеги, удалось посмотреть все три программы, присланные для участия в конкурсе. Буду смотреть ещё, работа не закончена, но пока могу сказать следующее:
Из трех программ пока не удалось получить неверный результат только от программы от Kolyn (но я не оставляю попыток :-))). Так что у нас есть лидер! У остальных, к сожалению неверные результаты проскакивают. Чтобы не быть голословным:
AndreyD, проверьте на вот таком тесте:
B707, посмотрите на вот такой тест. Решение получается пустым:
Это не единственные тесты, на которых работает неверно, но я предполагаю, что все они связаны с одной и той же ошибкой, потому остальные не выкладываю. Если это поможет в поиске ошибки - могу выложить.
Работа продолжается!
AndreyD, проверьте на вот таком тесте:
Признаю поражение.
Ошибка в строках 151-205 моего кода (блок // Удаляем более дорогие ключи с одним оставшимся размером, а если размера только два, второй ключ добовляем в результирующий набор, помечая размеры на удаление из этого ключа.)
Не до конца продумал логику.
Отсев дублей и последующий перебор сочетанями без повторений, видимо, более правильное решение. Так что, думаю, что у Kolyn все ответы будут правильными.
Попробуйте, пожалуйста, вот такой вариант на наборах, которые выдавали ошибочный ответ:
Попробуйте, пожалуйста, вот такой вариант на наборах, которые выдавали ошибочный ответ:
Имейте совесть! Не мешайте человеку подвести итоги конкурса - это большая работа.
Имейте совесть! Не мешайте человеку подвести итоги конкурса - это большая работа.
Я же не говорю прямо сейчас, и ни в коем случае не требую прямо сейчас. Я же уже выбыл из кандидатов на победу.
B707, посмотрите на вот такой тест. Решение получается пустым:
Это не единственные тесты, на которых работает неверно, но я предполагаю, что все они связаны с одной и той же ошибкой, потому остальные не выкладываю. Если это поможет в поиске ошибки - могу выложить.
не надо, ошибку нашел сразу.
Правильный ответ:
Обидно, что в версии, что закончил через 3 часа после закрытия турнира - этой ошибки нет.
Но тут уж ничего не попишешь. Выложил я на конкурс эту версию, поэтому что о других говорить.
Обидно конечно, столько ждать проверки - и вылететь на первом тесте :( ЭХХХ
пойду напьюсь
Попробуйте, пожалуйста, вот такой вариант на наборах, которые выдавали ошибочный ответ:
Попробуйте сами на вот этих пяти тестах. Для проверки результатов используйте любую из программ коллег - с этими пятью они обе справляются.
пойду напьюсь
Вы ж вроде, нас с дедом в этом деле никогда не поддерживали!
Вы ж вроде, нас с дедом в этом деле никогда не поддерживали!
это потому что у меня повода серьезного не было :)
это потому что у меня повода серьезного не было :)
Мне на эту тему очень картинко нравиццо
Вы ж вроде, нас с дедом в этом деле никогда не поддерживали!
это потому что у меня повода серьезного не было :)
начинать надо было с той моей задачи про бочку, кою решил (почти) только один человек на форуме (кроме дракулы, но я его решение даже понять не в состоянии, не то, что проверить) :-)))
начинать надо было с той моей задачи про бочку
Для конкурса крайне важно, чтобы Организатор и Жюри пользовались безусловным авторитетом в данной области... иначе итогом будет просто срач. как и сложилось в твоем случае.
Попробуйте сами на вот этих пяти тестах. Для проверки результатов используйте любую из программ коллег - с этими пятью они обе справляются.
Да приведённый мной код в сообщении #360 считает эти тесты правильно, НО проигрывает по времени коду kolyn , т.е. без правильной реализации моего пятого пункта словесного алгоритма я бы всё равно не выиграл.
Пятый пункт:
Ну, что коллеги, давайте подводить итоги.
Обработка результатов производилась в два этапа. На первом этапе я просто смотрел на программы и запускал их, чтобы проверить свои предположения об их поведении (правильно ли я их понимаю). На этом этапе обнаружилось, что две из трёх присланных программ содержат ошибки, которые проявляются на некоторых тестах.
Ну, как бы с победителем стало всё ясно, но это формальная сторона вопроса. В реальности же, ошибки мне показались непринципиальными (легко исправимыми), а идеи, заложенные в программах весьма интересными, поэтому я решил продолжить исследование (считая, что ошибки как бы исправлены) и выяснить вопросы скорости, тем более, что тем, кто предложит вариант быстрее моего, был обещать отдельный приз.
Последнее (предложение приза) было вполне реальным и я надеялся, что его кто-то выиграет, т.к. изначально я не планировал писать свою программу "на все деньги", т.е. добиваться того, чтобы она была как можно более эффективной. Вместо этого я планировал использовать только очевидные и бесспорные оптимизации и стандартные, "вездеупотребительные" методы. Т.е. я хотел написать учебный материал по общим методам решения задач такого рода и не более того. Такая программа заведомо должна проигрывать специализированным программам, использующим частные методы "под задачу". Её ценность в демонстрации общих методов, которые применимы для всех задач. Я рассчитывал, что кто-нибудь обязательно напишет более быстрое решение. Считаю, что в действительности так и получилось, несмотря на то, что формально это не так (см. ниже).
Таким образом, на втором этапе я нагенерил кучу случайных тестов, а именно - девяносто тестов с семью ключами, пятьдесят тестов с тринадцатью ключами и тридцать два теста с семнадцатью ключами и прогнал через эти 172 теста все программы, включая и свою - внеконкурсную.
Все использованные тесты приведены ниже (повторяю, они получились путём генерации псевдослучайных чисел).
Тесты с 7 ключами
Тесты с 13 ключами
Тесты с 19 ключами
Результат прогона всех четырёх программ представлен в таблице ниже. В первых двух столбцах количество ключей и номер теста - эта пара уникальна, так что Вы легко можете найти нужный тест в выложенных выше листингах. Цена в копейках, а время в микросекундах. На тех тестах, на которых та или иная программа выдала неверный результат, вручную проставлено время 10 000 000 мкс., что гарантирует, что они окажутся "хуже" любого правильного результата. Всего таких неудачных тестов 28. Пять у программы от AndreyD и 23 у программы от b707.
Ну, давайте сравним скорость работы.
Поскольку тест на тест не приходится, время сравнивалось по отдельным тестам, а потом уже обобщалось на "в среднем".
В таблице ниже слева просуммировано сколько раз (из 172 тестов) какая из программ оказывалась на первом, втором, третьем и четвёртом местах по времени исполнения. Справа же приведены интегральные показатели скорости программ участников, полученные таким образом: количество первых место умноженное на 3 (т.к. обошёл трёх соперников) + количество вторых мест, умноженное на 2 (из тех же соображений) + количество третьих мест (четвёртые не учитывались вовсе).
отсюда сразу же видно, что формально, в среднем моя программа быстрее, но если исправить ошибку в программе b707, то она окажется самой быстродействующей. Ведь 23 в графе "четвёртые места" - это как раз те самые 23 теста с которыми она просто не справилась. На тех тестах, с которыми она справлялась, она была в среднем быстрее всех. Обязательно посмотрите на эвристики, которые там используются, они очень красивые. Очень, надеюсь, что автор выложит ещё и пояснения к ней. Кстати, остальных участников тоже очень прошу выложить пояснения.
ну, а с точки зрения подведения итогов и вручения приза, надо отметить, что программы двух участников - b707 и AndreyD обходили мою на каких-то тестах, а потому им обоим полагается обещанный "спец. приз.".
Таким образом,
1. победителем признаётся Kolyn - единственный из участников, чья программа справилась со всеми тестами.
2. b707 и AndreyD получают специальные призы за то, что их программы оказались быстрее моей на нескольких тестах тестах.
Прошу всех троих прислать мне на elilitko@mail.ru координаты для отправки призов.
P.S.
собственную программу выкладываю, но хочу ещё снабдить её пояснениями, чтобы превратить в учебный материал, но на это уже нет сил - завтра пояснения выложу. Если кто-то захочет её скорость измерять на своих тестах, не забудьте все печати закомментировать. Программа состоит из 4-х файлов. Для печати отладочной информации нужна библиотека Printing.
Файл .ino
Файл Sizes.h
Файл Solution.h
Файл Spanner.h
------------------------------------
Действительно Гигантская проведена работа по подведению итогов конкурса, аплодирую стоя.
Очень, надеюсь, что автор выложит ещё и пояснения к ней. Кстати, остальных участников тоже очень прошу выложить пояснения.
Уточните, пожалуйста, какие пояснения нужны, я выкладывал словесный алгоритм и старался подписывать комментариями все блоки кода.
Евгений, спасибо огромное за проделанную работу, ну и за похвалу тоже :)
Можете уточнить, как технически вы прогоняли такое количество тестов на наших кодах? Не вставляли же в исходник каждый раз?
Я вижу что у вас коды организованы в массив - но если вставить этот массив в код . у атмеги оперативки вовсе не останется.
Уточните, пожалуйста, какие пояснения нужны, я выкладывал словесный алгоритм и старался подписывать комментариями все блоки кода.
Вероятно, я пропустил. Поищу завтра.
Вероятно, я пропустил. Поищу завтра.
Сдублирую с небольшими изменениями (ещё немного дополнил):
Дополнительно:
1) Ссылки и указатели не использовал, т.к. не умею
2) От рекурсии отказался, т.к. она требует много памяти, решил что количество ключей важнее.
3) От размещения массива ключей (размеры и цена) в PROGMEM отказался, т.к. подумал, что это повлияет на скорость выполнения кода.
ЗЫ: много раз менял словесный алгоритм, теперь, вроде, полностью описал. А то был День трезвости, надо было отметить праздник. ))
Взял мегу и написал вот такой "испытательный стенд"
Файл "TestBench.h"
Файл "TesBench.h" вместе с тремя файлами тестов кидал в папку с испытуемой программой.
В испытуемой программе делал три вещи
1. Вместо определения массива spanners вставлял #include "TestBehcn.h"
2. Перед засечением времени вставлял строку "setupTest();"
3. После отсечки времени, убирал печать результата, а вместо неё вставлял вызов "printTestRes(price, duration);"
(ну, с точностью до имён переменных - принцип такой).
Далее, в файле "TesBench.h" в десятой строке вставлял ник, а в восьмой имя файла с тестами и запускал. Она выполнял один тест, после чего сама ресетилась и выполняла следующий и так пока все не выполнит. Когда все тесты отработают, в мониторе аккуратная табличка.
Моя IDE не стирает монитор при перезаливке, так что ничего не трогая менял имя файла с тестами и запускал ещё раз. Результаты приписывались в хвост.
Когда все тесты для одного участника выполнены, тупо копировал их прямо в Excel (там не зря через табуляции всё выводится).
И так для всех участников.
Ну, а дальше в экселе обрабатывал.
P.S.
Ах, да, я там выше выкладывал только сами тесты. Если кто захочет "стенд" запускать, то полностью файлы тестов выглядят примерно так:
Класс!!!
не знаю, стоит ли писать... интересно ли это кому-нибудь. Но хочется "излить душу"...
Ошибка в моей программе особенно обидна тем, что она не в алгоритме, а исключительно в синтаксисе.
Проявляется она в том, что если при начальной обработке набор не удалось упростить (то есть не выбрано и не выкинуто ни одного ключа) - процедура предварительной обработки ошибочно рапортует о найденном решении без ключей и с нулевой ценой :)
А еще это обидно потому, что, после исправления - именно на таких сложных наборах мой алгоритм мог бы сильнее всего опередить других участников.
вот, для примера посчитал несколько вариантов, на которых в тестах Евгения мой код спасовал:
Это поучительная история о том, что в отладке программ никогда не бывает слишком много тестов. Приходится только удивляться, что мы втроем тусили тут в ветке почти месяц - и за это время никому не пришло в голову попробовать набор ключей, который не содержит дублей или очевидных кандидатов на вылет. и только когда Евгений сказал об ошибке - я нашел ее буквально за минуты...
Однако, результат есть результат.
Как говорят в футболе, "Красота игры забывается, в истории остается только счет" - кажется Лобановский
Приходится только удивляться, что мы втроем тусили тут в ветке почти месяц - и за это время никому не пришло в голову попробовать...
Увы, недоотлаженность - эта общая проблема всего современного программного обеспечения. При этом, что с этим делать - никто не знает. Формальные методы накладывают слишком серьезные (неприемлемые) ограничения на язык программирования, а все что остается вне этих рамок - во-первых, приводит к трудозатратам на отладку и тестирование порядка 80-90% от всей стоимости разработки, а во-вторых, совершенно не гарантируют результата.
Еще раз хотелось бы высказать свое восхищение Евгением Петровичем. Во-первых тем, что он, зная, какая предстоит работа, все-таки взялся за этот конкурс, и во-вторых, тем, как он его провел. Собственно, только после публикации результата большинству участников и наблюдателей стало понятно, что работа жюри по своему объему превышает работу всех участников конкурса вместе взятых.
Ну и еще одно наблюдение, имеющее, на мой взгляд, универсальный характер: уровень разработки современного ПО таков, что приходится выбирать только между "правильно, но крайне медленно" и "быстро, но неправильно".
Upd: Да, забыл, что еще, как правило, не обходится и без варианта "медленно и неправильно".
Как говорят в футболе, "Красота игры забывается, в истории остается только счет" - кажется Лобановский
Терпеть не могу эту фразу. Ещё (ровно из той же оперы) не люблю, когда нетренеры цитируют тренерское: "Игрок на площадке должен работать". Мне гораздо больше нравится, когда игроки играют, а не работают. Сравните немцев и бразильцев, чтобы понять о чём я. А Лобановский - он из тех же соображений и договорняки оправдывал: "Не понимаю, почему шахматисты могут согласиться на ничью, а нам нельзя". А о том, что я пришёл на трибуну игру смотреть, а не договорную тухлятину он как-то, видимо, не подумал.
Вообще, "победа любой ценой" - это неспортивно. По мне так Игорь Нетто, сказавший судье о неправильно забитом голе в матче чемпионата мира - великий Спортсмен, а Марадона с его "рукой Господа Бога" просто мячик пинал здорово, но Спортсменом не был.
Только деньги платят за результат, а не за красивую игру. К сожалению.
Не ожидал! Я уже и за темой не следил, думал, что на последнем месте. По факту - моя программа медленнее всех, и если бы не ошибки других участников первого места мне бы не видеть.
Спасибо организатору конкурса за проделанную работу. Особенно впечатлил подход к подведению итогов .
Евгений Петрович, спасибо!
b707, прокомментируйте пожалуйста свой код, хотя бы кратко.
По мне так Игорь Нетто, сказавший судье о неправильно забитом голе в матче чемпионата мира - великий Спортсмен, а Марадона с его "рукой Господа Бога" просто мячик пинал здорово, но Спортсменом не был.
так всё таки рукой )))
пропустил, не являюсь болельшиком, по мне больше технические виды, спидвей к примеру...
PS эпизод этот помню...
Выкладываю программу с комментариями
При оптимизации наборов ключей я решил трактовать их как граф, где вершинами являются размеры, а ребрами - сами ключи. Причем основная оптимизация делается через работу с вершинами, поэтому первым делом я считаю число уникальных размеров в наборе и создаю табличку вершин, для каждой из которых храню число ребер и их список. Признаком того, что вершина выбрана - является равенство нулю числа ребер на вершине.
Для ключей тоже создаем временную табличку, где храним индекс ключа и флаг, выбран ключ или нет. Первоначально я - по примеру других участников - хранил и признак того, что ключ исключен из набора. Но потом пришел к тому, что исключенные ключи эффективнее просто стирать из таблицы , что уменьшает необходимую память и увеличивает скорость работы.
На этапе создания таблиц так же избавляемся от дублей, оставляя только самые дешевые ключи для заданных комбинаций.
Первоначальная обработка (А) использует те же подходы, что и в алгоритме Андрея.
A.1 - выбираем ключи, размер которых представлен в единственном экземпляре
А.2 - исключаем ключи, лежащие между двумя уже выбранными вершинами
А.3 - находим ключи, один конец которых выбран, а другой нет. Если этот ключ не является самым дешевым для данного размера - его можно выкинуть, более дешевый ключ точно будет выгоднее
Шаги А.1 - А.3 повторяем в цикле до тех пор, пока граф не перестанет меняться. После этого аналитическая часть закончена, переходим к итерациям.
Для начала проверяем, не является ли набор полным, и если да - новым минимумом. Если так - запоминаем найденный набор и выходим из этой итерации.
Алгоритм дальнейшего поиска таков.
(шаг В) Выбираем любую из еще не включенных в набор вершин. Очевидно, что у любой такой вершины как минимум 2 ребра, иначе она была бы уже выбрана. И так же очевидно, что как минимум одно из этих ребер должно войти в оптимальный набор. Все что нам нужно - это посчитать наборы для каждого из этих ребер и сравнить их между собой. Фактически можно сказать. что вся задача упрощается до сравнения двух-трех вариантов ;)...
(шаг С) выбираем любое из ребер данной вершины, считаем его включенным в конечный набор и решаем для полученного набора (который стал чуть меньше) исходную задачу - то есть сначала выполняем шаги А, потом шаг Б и шаг С рекурсивно все глубже и глубже, пока не будет набран полный набор.
Далее поднимаемся обратно до текущего уровня шага С, выбираем следующее ребро и ищем минимум для него. Минимальный из этих наборов и будет глобальным минимумом.
Тут есть некоторые уточнения, которые помогают сделать поиск эффективнее. На шаге В выгоднее выбирать не любую вершину, а такую, что лежит максимально удаленно от концов графа - тогда при выборе ребра, прилежащего к вершине - на стадии А есть шанс больше упростить граф, чем если вершина крайняя. Для этого используется функция find_center()
И второе - ребра лучше тестировать в порядке возрастания стоимости, тогда есть шанс найти минимальное решение как можно раньше и все остальные ветки просто отсеять, когда, в ходе итерации, их текущая цена превысит найденный минимум.
Остальное - в комментах. Задавайте вопросы.
А.3 - находим ключи, один конец которых выбран, а другой нет. Если этот ключ не является самым дешевым для данного размера - его можно выкинуть, более дешевый ключ точно будет выгоднее
До этого сам не дошел, а когда Андрей написал п.5:) посчитал не совсем честным включать в свой алгоритм.
Алгоритм дальнейшего поиска таков.
(шаг В)
(шаг С)
Снимаю шляпу!
До этого сам не дошел, а когда Андрей написал п.5:) посчитал не совсем честным включать в свой алгоритм.
Время работы моей программы с первого результата (выложенного впервые в #166) почти не изменилось - на наборе ЕП тогда было 2.8мс, а в текущей версии - 2.53мс. Это потому что основной алгоритм сложился сразу и в дальнейшем я его почти не менял, потратив две недели на борьбу с нехватками памяти.
Насколько я вижу в обсуждении, первый раз о своем "пункте 5" Андрей заговорил лишь спустя 10 дней. Так что мы с ним пришли к этой хитрости независимо.
До этого сам не дошел, а когда Андрей написал п.5:) посчитал не совсем честным включать в свой алгоритм.
Время работы моей программы с первого результата (выложенного впервые в #166) почти не изменилось - на наборе ЕП тогда было 2.8мс, а в текущей версии - 2.53мс. Это потому что основной алгоритм сложился сразу и в дальнейшем я его почти не менял, потратив две недели на борьбу с нехватками памяти.
Насколько я вижу в обсуждении, первый раз о своем "пункте 5" Андрей заговорил лишь спустя 10 дней. Так что мы с ним пришли к этой хитрости независимо.
Это я только о своем коде. Не подумайте плохого:))
Это я только о своем коде. Не подумайте плохого:))
я для определенности :)
На самом деле я был очень раздосадован, когда прочитал, что Андрей тоже нашел этот ход :) Потому что этот п5 очень полезная штука :)))
Насколько я вижу в обсуждении, первый раз о своем "пункте 5" Андрей заговорил лишь спустя 10 дней. Так что мы с ним пришли к этой хитрости независимо.
Да, чужой код анализировать это ещё то удовольствие. ) Ещё и когда мало знаешь язык программирования. Да и принципиально хотел разработать своё решение отличное от других, а не пытался разобраться в других кодах.
Коллеги, получил координаты двух участников, а от b707 ничего нет. Напишите. пожалуйста, чтобы мне на почту один раз ходить, а не несколько.
----------------------------------
Обещанные пояснения к моей программе.
Итак, как уже говорилось - использовались только общие методы, применимые практически для любой переборной задачи, а именно: сначала пытаемся сократить количество перебираемых элементов, удаляя очевидно ненужные, а затем перебираем оставшиеся, используя метод ветвей и границ. Сокращение элементов для перебора делается в три шага.
Итак, первое - удаляем дубликаты. На самом деле для данной задачи это скорее вредно, чем полезно, т.к. вряд ли стоит ожидать, что магазин выложит несколько одинаковых ключей в одном лоте, поэтому это скорее потеря времени, чем его экономия. Но, сказано же - общие методы - делаем. Для удаления дубликатов в строке №143 вызывается функция removeDuplicates(), сама функция находится в строках №№40-49. Первое, что в ней делается - сортировка (стр. №41) по следующим критериям: по первому размеру, если равно, то по второму размеру, если и здесь равенство, то по цене. Всё в порядке возрастания. Сама по себе сортировка живёт в файле Spanner.h и она совершенно стандартная с использованием стандартной функции qsort, поэтому я на ней не останавливаюсь. В результате сортировки все одинаковые ключи окажутся рядом, причём первым будет идти самый дешёвый. Поэтому, далее мы проходим по получившемуся массиву ключей и помечаем как Spanner::stateDuplicate все элементы, которые не уникальны, кроме первого из группы. Это сделано в строках №№43-46. Функция возвращает количество помеченных ключей.
Далее в строках №№144-146 выполняются некие служебные действия, а именно инициализируются две структуры "решения". Одна для "текущего", а другая для "наилучшего". Им передаётся количество уникальных ключей (которая является верхней оценкой количества ключей в решении, больше не будет). И ( в строке №146) создаётся массив всех имеющихся размеров. Всё это нам пригодятся в будущем.
Второе - находим точно ("железобетонно") входящие в решение ключи и формируем начальное решение из этих ключей. Под "жеоезобетонными" мы понимает те ключи, у которых хотя бы один из размеров больше нигде не встречается. Такой ключ обязан быть в решении, иначе в нём не будет этого размера.
Для этого в строке №148 вызываем функцию markFerroconcretesAndFormSolution (сама функция находится в строках №№70-100). Функция возвращает true, если всё в порядке и false, если не хватило памяти. Как видите я забыл после неё проверить, что она вернула, торопился. Также функция помещает в свой первый параметр (по ссылке) количество таких "железобетонных ключей", которые она нашла. Давайте на неё смотреть.
Для начала она запрашивает массив размеров на максимально возможно количество (строки №№71-75), затем проходит по всему массиву ключей и считает сколько и каких размеров там присутствует. Для этого она для каждого из размеров ключа во-первых, увеличивает счётчик, а во-вторых запоминает в каком именно ключе такой размер встретился (строки №№77-84). Таким образом, после строки №84 в массиве sizesArray мы для каждого размера имеем количество (сколько раз он у нас встречается) и адрес последнего из найденных ключей в котором такой размер встречался.
Ну, осталось только пометить как "железобетонные" те ключи, у которых хотя бы один размер встретился только один раз и добавить их в "базовое решение". Это делается в строках №№87-97. Попутно считается общее количество имеющихся размеров (строки №№ 86 и 90) - оно нам пригодится для оценки полноты найденных решений. Обратите внимание на крайне неудачный комментарий в строке №91 (только сейчас заметил, но поправить уже не могу - ответили). Этот комментарий скрывает очень важный момент. Там ведь проверяется не то, что это дубликат, а то, что ключ не имеет никакого "ненулевого" состояния. Это важно! Если бы мы здесь проверяли только на "дубликат", то ключи у которых оба размера уникальны и больше нигде не встречаются, попадали бы в базовое решение дважды (были бы "дважды железобетонными"), но поскольку мы проверяем на "ненулевое состояние", ключ, который уже помечен как "железобетонный" здесь будет отсеян и второй раз в решение не попадёт.
После завершения формирования базового решения (содержащего только железобетонные ключи), стоит проверить, а не является ли оно полным, т.е. не содержит ли всех размеров? Если так, то нам сильно повезло и ничего больше делать не нужно - оптимальное решение найдено. Такая проверка делается в строке №152. Если же нет, продолжаем работу - строки №№155-162
Наконец, третье - теперь, когда у нас уже есть "базовое решение", содержащее только железобетонные ключи, мы можем проверить все оставшиеся ключи на предмет: а не присутствую ли оба размера данного ключа в базовом решении. Если присутствуют, то такой ключ нам неинтересен - он никак не может попасть в оптимальное решение и мы будем помечать его как бесперспективный. Это делается вызовом функции excludeHopelessSpanners() (строка №155), которая делает всё, о чём говорилось и возвращает количество ключей помеченных как бесперспективные. Сама функция в строках №№106-117 и она достаточно прозрачна и очевидна.
Таким образом, после строки №155 у нас имеется базовое решение, содержащее железобетонные ключи, а все ключи в наборе помечены либо как дубликаты, либо как железобетонные, либо как бесперспективные, либо не помечены никак. Вот именно последние, никак не помеченные, и будут участвовать в переборе. Здесь, для удобства мы ещё раз пересортируем ключи в наборе таким образом, чтобы никак не помеченные ключи оказались в самом начале и при этом были отсортированы по возрастанию цены (это очень важно для применения метода ветвей и границ). Вызов сортировки в строке №158. Сама по себе сортировка, опять же, достаточно проста и очевидна, мы на неё не будем останавливаться. И, наконец, в строке №59, мы запишем в переменную totalSpanners количество оставшихся (никак не помеченных) ключей.
Всё! Мы готовы к перебору. Перебирать будем totalSpanners первых элементов массива spanners.
Перебором занимается функция lookForSolution (строки №№ 122 - 135). Она выполняет перебор методом ветвей и границ, который я подробно описывал в посте #228, поэтому здесь описывать не буду.
На закуску несколько замечаний о сортировке:
1.
при работе со структурированными данными, сортировка - наше всё! Мы применили её дважды, но если бы массивы ключей были большими, то стоило бы применить ещё пару раз, чтобы избавиться от проверок в строках №№ 79, 91 и 110. Т.е. сортировка - главный инструмент в задачах со структурированными данными - она облегчает и ускоряет всё. Да собственно и по жизни, вот прикиньте (особенно в момент, когда чистите снег во дворе или прибираетесь в мастерской), сколько времени своей жизни мы посвящаем перекладыванию предметов с места на место!
2.
Я использую функцию qsort - это хорошая быстрая сортировка, но для маленьких массивов она вполне может проигрывать обычному "пузырьку". Если бы стояла цель выжать всё возможное быстродействие, стоило бы сравнить эти два метода и выбрать лучший для данного случая.
3.
Обычно в задачах, где данных много или они сами по себе большие, никто и никогда не сортирует сам массив данных, как я это делал здесь. Вместо этого заводят массив указателей (на ключи) и сортируют его. Это просто необходимо, когда, например, нам нужно иметь наши данные одновременно отсортированными по разным критериям, или когда наши данные сидят в read-olny памяти и мы не можем их перемещать. Просто заводим столько массивов указателей, сколько критериев, и сортируем эти массивы, а сами данные не трогаем вовсе.
Коллеги, получил координаты двух участников, а от b707 ничего нет.
написал
и некоторые замечания по сортировке
Первое, что в ней делается - сортировка (стр. №41) по следующим критериям: по первому размеру, если равно, то по второму размеру, если и здесь равенство, то по цене. Всё в порядке возрастания.
сортировка всех ключей сначала по одному размеру, потом по второму... мне показался такой подход переусложненным. Я преобразую размеры каждого ключа в целое uint16_t, где старший байт это меньший размер, а младший - больший. И потом сравниваю полученное целое с аналогичным индексом для других ключей простым перебором, сразу же выкидывая дубликаты. Возможно, сортировка массива индексов дала бы небольшое ускорение, но эту процедуру надо выполнить всего один раз, да и массивы у нас по определению не больше нескольких десятков ключей...
что касается основного поиска минимума, вы пишете:
Хочу сказать о выделенной фразе. Я в своей программе сознательно отказался от сквозной сортировки всего массива. Как мне кажется... это бессмысленная трата времени. Какой смысл в том, что ключ 12х14 стоит дешевле, чем 22х24 ? - это же разные ключи, они не заменяют друг друга...
Сортировка имеет смысл только среди ключей одного размера. В случае же общих индексов я сначала делал их упорядоченными, а потом от этого отказался и выиграл во времени. Затраты на поддержания порядка в сортированном индексе съедают больше ресурсов, чем дают преимуществ. Вставка элемента в упорядоченный индекс требует поиска верной позиции для вставки и сдвига остальных элементов "ниже". В не сортированный - вы просто добавляете элемент в конец. Тоже самое с удалением - после удаления элемента из середины списка вам придется сдвинуть все остальные на позицию вперед, в несортированном же вы просто перемещаете последний элемент на место удалененого. В среднем любые операции с несортированным списком в N/2 раз быстрее, чем с сортированным. ... за исключением поиска конкретного элемента. Но как раз поиск конкретного ключа в общем индексе нам вообще не требуется...
... после удаления элемента из середины списка вам придется сдвинуть все остальные на позицию вперед, в несортированном же вы просто перемещаете последний элемент на место удалененого. В среднем любые операции с несортированным списком в N/2 раз быстрее, чем с сортированным. ... за исключением поиска конкретного элемента. Но как раз поиск конкретного ключа в общем индексе нам вообще не требуется...
А ведь и правда, а я при удалении элемента в несортированном массиве сдвигал все последующие друг за другом до конца массива.
Коллеги, получил координаты двух участников, а от b707 ничего нет. Напишите. пожалуйста, чтобы мне на почту один раз ходить, а не несколько.
Просьба ещё сообщить номер для отслеживания, а то у меня почтальоны ленивые, могут в течении месяца только сообщить, что мене что-то пришло.
Ждём следующий конкурс для профессионалов!!!
Значит Вы не поняли основную идею метода ветвей и границ. Смысл сортировки по цене именно в том, что если какой-то ключ вылез за текущую минимальную цену, то остальные можно не перебирать. Если они не были отсортированы - это бы не работало. Прочитайте ещё раз описание метода, если не поможет, скажите, я сделаю новое с примерами и картинками.
Ну, у нас просто методы разные. При переборе мне не нужно поддерживать порядок в массиве по той простой причине, что я его не нарушаю - я не трогаю массив вообще.
Значит Вы не поняли основную идею метода ветвей и границ. Смысл сортировки по цене именно в том, что если какой-то ключ вылез за текущую минимальную цену, то остальные можно не перебирать.
а, ну да... тогда согласен. Идея полного перебора, без предварительного учета размеров, как-то никак не укладывается у меня в голове. Я ее вообще не принимаю в расчет :) Поэтому и вопросы такие глупые...
Рассказал троим моим знакомым (друзьям, родственникам) о конкурсе, один точно подтвердил, что будет участвовать в будущих конкурсах (контроллеры у него уже есть), два других пока не знаю будут ли, у них ардуиновских наборов нет, но я себе заказал nano every Atmega4808 две штуки (в январе должны прийти), в замен моих находящихся в работе Nano v3.0, так что если что смогу им отдать нанки под конкурс, плюс ещё что потребуется для будущего конкурса из моих запасов, ну если они захотят участвовать.
Дополнение:
Чёт я поторопился заказать Nano Every Atmega4808, пишут фигня ещё та, отменил заказ. Заказал Arduino Nano Every ATMega4809, но одну штуку на пробу.
Ждём следующий конкурс для профессионалов!!!
Очень сомневаюсь в реальности. Большие дяди меряются заковыристыми алгоритмами. А такие алгоритмы демонстрируют свои преимущества при больших наборах данных, что для ардуины проблемно. На малых же наборах они даже перебору данных проигрывают. Глянувши на этот конкурс, видно что более менее продуманный алгоритм у b707 сделан, и он бы порвал 'переборщиков' всех мастей, будь наборы данных побольше и стека ардуины поглубже. А в целом - печалька, даже слона по частям съесть не сумели. Не смотря на ваши мольбы и просьбы )))
для профессионалов!!!
Давно бредится в голове идея игры (отдалённо типа "Core War"), даже пробовал пару раз. Но не получается интересной. Либо слишком сложная, либо слишком простая (с ничейной смертью).