EduBrick

Ввод и вывод

Замеры: cin без ускорения читает два миллиона чисел 395 мс, с ускорением — 71. Плюс getline, точность вывода и чтение до конца файла.

4 мин

Ввод-вывод — единственная часть программы, которая может превысить лимит времени, даже когда алгоритм оптимален. Разница между ленивым и аккуратным вариантом — пять раз.

Замеры

Чтение двух миллионов целых чисел:

способ время
cin как есть 395 мс
cin + sync_with_stdio(false) 71 мс
scanf 97 мс

Вывод миллиона строк:

способ время
cout << i << '\n' 39 мс
cout << i << endl 201 мс

Замерено на одной машине, GCC с -O2. Абсолютные значения у вас будут другими, соотношения — те же.

Выводы простые: две строки ускорения делают cin быстрее scanf, а endl вместо '\n' замедляет вывод впятеро.

Две строки в начале main

ios::sync_with_stdio(false);
cin.tie(nullptr);

Первая снимает синхронизацию с потоками языка C. По умолчанию каждая операция cin согласуется с scanf, чтобы их можно было смешивать, — за это и платится четырёхкратная разница.

Вторая развязывает cin и cout. Без неё перед каждым чтением автоматически сбрасывается буфер вывода.

Две оговорки:

  • после этого нельзя смешивать cin/cout со scanf/printf — порядок перестанет быть предсказуемым;
  • в интерактивных задачах cin.tie(nullptr) писать нельзя: там автоматический сброс перед чтением — единственное, что спасает от взаимной блокировки.

endl против '\n'

endl — это перевод строки плюс сброс буфера. Сброс означает системный вызов: данные уходят операционной системе немедленно, вместо того чтобы копиться и уйти пачкой.

В обычной задаче это чистые потери. Пишите '\n'.

Ровно одно исключение — интерактивные задачи, где сброс необходим после каждого запроса.

Вещественный вывод

По умолчанию cout печатает шесть значащих цифр и обрезает хвостовые нули:

double d = 1.0 / 3;
cout << d;                                  // 0.333333
cout << fixed << setprecision(10) << d;     // 0.3333333333

setprecision без fixed задаёт число значащих цифр, с fixed — число знаков после запятой. В задачах почти всегда нужно второе.

Ставить обе манипуляции достаточно один раз в начале — они действуют до конца программы.

Практическое правило: выводите на два-три знака больше, чем просят. Если требуется точность 10610^{-6}, печатайте девять знаков. Лишние цифры не вредят, а обрезанные — теряют точность, которую вы честно посчитали.

Отдельная ловушка: cout << 1e20 без fixed напечатает 1e+20, и чекер это не примет. Для больших вещественных ответов fixed обязателен.

Чтение до конца файла

Когда количество чисел не задано:

int x;
long long sum = 0;
while (cin >> x) sum += x;

Оператор >> возвращает поток, а тот приводится к bool: истина, пока чтение удаётся. Работает и для конца файла, и для нечисловых данных.

getline и ловушка с переводом строки

cin >> s читает слово — до первого пробела. Чтобы прочитать строку целиком, нужен getline(cin, s).

Ловушка возникает при смешивании:

int n;
cin >> n;              // прочитал число, перевод строки остался в потоке
string line;
getline(cin, line);    // прочитал ПУСТУЮ строку — остаток предыдущей
getline(cin, line);    // вот теперь настоящая строка

Проверено: первый getline возвращает пустую строку, второй — настоящую. Причина в том, что >> останавливается перед переводом строки, не съедая его, а getline читает до ближайшего перевода — и сразу его находит.

Лечится либо холостым getline, либо cin.ignore() после числового чтения.

Файловый ввод для отладки

Гонять тест руками через консоль неудобно. Перенаправление ввода:

#ifdef LOCAL
    freopen("input.txt", "r", stdin);
    freopen("output.txt", "w", stdout);
#endif

Компилируется с -DLOCAL, на сервере блок отсутствует. Забыть убрать freopen перед отправкой — классика, а #ifdef эту возможность убирает.

Отладочный вывод при этом должен идти в cerr: он не перенаправлен и останется на экране.

Когда нужен свой ввод

Если после всех ускорений всё равно не хватает — а это бывает на входах порядка 10710^7 чисел, — читают весь ввод одним куском и разбирают вручную:

static char buffer[1 << 25];
fread(buffer, 1, sizeof(buffer), stdin);
// дальше разбираем buffer посимвольно

Это последнее средство. На школьных олимпиадах оно почти никогда не требуется: если решение не проходит, дело обычно в алгоритме, а не в чтении.