Школа программиста

Забыли пароль?
[задачи] [курсы] [олимпиады] [регистрация]
Логин:   Пароль:    
Скрыть меню
О школе
Правила
Олимпиады
Фотоальбом
Гостевая
Форум
Архив олимпиад
Архив задач
Состояние системы
Рейтинг
Курсы
Новичкам
Работа в системе
Курсы ККДП
Дистрибутивы
Статьи
Ссылки


 
Вернуться
Тема: Двоичный поиск в задаче Всероссийская олимпиада по информатике
1 2
  21  Егоров Илья Валерьевич, 15 сентября 2020 г. 3:04:16
      ...тора неровностей за O(1), о которых мною сказано ранее.
  22  Егоров Илья Валерьевич, 15 сентября 2020 г. 3:04:01
      Пожалуй, не буду ждать и прямо отвечу на вопрос. Как я полагаю, основная причина кроется в передаче по значению vector<int>. Покажу на простом примере (где ~~ обозначает 1 отступ):

#include <vector>

static void test(std::vector<int> a) {
~~// none
}

int main() {
~~const int N = 100000;
~~
~~std::vector<int> a(N);
~~
~~for (int i = 0; i < N; i += 1) {
~~~~test(a);
~~}
}

Как может показаться на первый взгляд, этот код ничего не делает. На самом деле это не так: тут N раз забавы ради копируется вектор с N нулями, поскольку передается он по значению. В силу сохранения побочных эффектов (злейшего врага ряда разработчиков) компилятор не может отбросить такое даже на -O3, а уж с -O2 и остальным вагоном указанных в "Работа в системе" параметров выдает в godbolt полноценные 87 строк ассемблера.

Возьмем упрощенную модель: инициализация памяти, равно как и копирование, по сложности составляет O(n), что образно похоже на простой поэлементный форик. Это не далеко от истины, даже если предположить возможные оптимизации со стороны стандартной библиотеки. Итак, вместе с последним фориком принимаем асимптотическую сложность приведенного кода равной O(n^2). На моей машине выполняется такой код за условные ~1.333 сек.

Что будет, если заменить передачу по значению на передачу по ссылке (приписать &)? А вот что: компилятор возьмет да спокойно отбросит и функцию, и сам форик, оставив только инициализацию памяти для вектора. В предыдущем абзаце она была упомянута не просто так: компилятор не может просто взять и выбросить вектор. В конструкторе вектора происходит явный вызов memset, что и проглядывается в получаемом выхлопе на ассемблере в 14 строк. Указанную сложность также приводит cppreference. Получаем O(n), что выполняется менее чем за сотую долю секунды и превышает секунду лишь с добавлением к N аж 4 нулей.

Вывод: вместо ожидаемого вами O(nlogn) ваше решение, благодаря копированию вектора, получилось за квадрат, к чему также приплелись отягчающие обстоятельства в виде небольших неудобных для компиля
  23  Егоров Илья Валерьевич, 15 сентября 2020 г. 0:47:14
      Вдобавок скажу, что если нет определенных навыков и/или понимания "что есть излишний функционал в стандартных функциях и как таковой спилить", то предпочтительно использовать упомянутые стандартные функции, да структуры данных подбирать с умом. Если, конечно, не созрела кристально чистая мысль наточить свое мастерство, даб стать способным писать действительно эффективные решения. Как ни как, незначительные детали тоже влияют на итоговую асимптотическую сложность (как пример подумайте к чему приводит ваше "vector<int> a" в контексте функции), хоть иногда подобное упускается из виду в силу гипотетической неявности (а на самом деле просто набитости руки с сопутствующим непониманием происходящего) и приводит к таким ситуациям, как почти полная неприменимость комика-змеи в условиях определенных задач.
  24  Егоров Илья Валерьевич, 15 сентября 2020 г. 0:01:42
      А тут и нечего удивляться. Передача по значению, чрезмерные вызовы .size(), да и сама функция ни inline, ни static (что в контексте глобальном как правило (но не во всех случаях) эквивалентно по действию inline) — все это несет определенный оверхэд. И что уж говорить о том, что binary_search — функция шаблонная через итераторы, с не самым примитивным алгоритмом, так что компилятором оптимизируется на ура, в то время как ваша реализация даже в банальном построении ветвления проигрывает: нет никакого значимого довода первым проверять a[mid]==x, когда true достигается только в конце алгоритма. Но это лишь догадки, т.к. вашим решением я не располагаю, однако величина разницы вполне реальная: в моем любимом 224 не совсем банальная оптимизация ввода-вывода ускоряет решение за постоянное O(n) в ~33 раза. Хотите докопаться до истины — лучше сами проверьте, пробуя менять узкое место своего кода.
  25  Зараник Богдан Юрьевич, 14 сентября 2020 г. 21:52:46
      За счёт чего ТАКОЙ выигрыш по времени? Ну я ещё поверил, если бы было 0,842 vs 0.65. НО в 28 раз!
  26  Зараник Богдан Юрьевич, 14 сентября 2020 г. 21:44:18
      Ребята, вопросик такой: написал задачу 638. O(nlogn). Если кто знает, как решить за О(n), намекните плиз). Но я не за то спрашиваю. Написал бинпоиск (вполне нормальный)
bool bs(vector<int> a, int x){
if(a.size()==0)return 0;
int l=0, r=a.size()-1, mid;
while(r-l>1){
mid=(l+r)/2;
if(a[mid]==x)return 1;
else if(a[mid]>x)r=mid;
else l=mid;
}
return (a[r]==x || a[l]==x);
}
Вся программа работает за 0,842.
Написал с binarysearch(встроенный) - 0.03!!!!
Вопрос один: как такое может быть?
1 2

Чтобы оставить сообщение необходимо зарегистрироваться и авторизоваться!

Красноярский краевой Дворец пионеров, (c)2006 - 2024, ИНН 246305493507, E-mail: admin@acmp.ru