Ввод и вывод
Замеры: 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 — число знаков после запятой. В задачах почти всегда нужно второе.
Ставить обе манипуляции достаточно один раз в начале — они действуют до конца программы.
Практическое правило: выводите на два-три знака больше, чем просят. Если требуется точность , печатайте девять знаков. Лишние цифры не вредят, а обрезанные — теряют точность, которую вы честно посчитали.
Отдельная ловушка: 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: он не перенаправлен и останется на экране.
Когда нужен свой ввод
Если после всех ускорений всё равно не хватает — а это бывает на входах порядка чисел, — читают весь ввод одним куском и разбирают вручную:
static char buffer[1 << 25];
fread(buffer, 1, sizeof(buffer), stdin);
// дальше разбираем buffer посимвольно
Это последнее средство. На школьных олимпиадах оно почти никогда не требуется: если решение не проходит, дело обычно в алгоритме, а не в чтении.