Редакция для Двойной двоичный код в строку


Remember to use this editorial only when stuck, and not to copy-paste code from it. Please be respectful to the problem author and editorialist.
Submitting an official solution before solving the problem yourself is a bannable offence.

1. Идея

Строку закодировали два раза одним и тем же способом:

  1. каждый символ исходной строки заменили его 8-битным ASCII-кодом;
  2. затем каждый символ получившейся двоичной строки, то есть символ '0' или '1', снова заменили его 8-битным ASCII-кодом.

Нужно выполнить обратную операцию тоже дважды.

Ключевая мысль: на втором этапе кодировались только символы '0' и '1'. Их ASCII-коды известны:

  • '0'00110000
  • '1'00110001

Значит, если разбить вход на блоки по 8 бит, каждый блок можно превратить обратно в символ '0' или '1'. Так мы восстановим промежуточную двоичную строку.

После этого остаётся ещё раз декодировать её: снова разбить на блоки по 8 бит, и каждый такой блок перевести в ASCII-символ исходного сообщения.


2. Наблюдения

Наблюдение 1

Так как на втором этапе каждый символ промежуточной строки заменяется 8 битами, итоговая строка состоит из блоков длины 8.

Причём каждый блок — это либо:

  • 00110000, что означает символ '0',
  • 00110001, что означает символ '1'.

Наблюдение 2

После первого обратного шага получится строка, состоящая только из символов '0' и '1'. Это и есть результат первого кодирования.

Её длина кратна 8, потому что на первом этапе каждый символ исходной строки тоже заменялся 8 битами.

Наблюдение 3

Чтобы восстановить символ по 8-битной двоичной записи, можно просто посчитать её численное значение в двоичной системе:

  • идём слева направо;
  • текущий value умножаем на 2;
  • прибавляем текущий бит 0 или 1.

После этого value — это ASCII-код символа.


3. Алгоритм

  1. Считать строку s.
  2. Восстановить промежуточную строку first:
    • идти по s блоками по 8 символов;
    • если блок равен 00110000, добавить в first символ '0';
    • если блок равен 00110001, добавить в first символ '1'.
  3. Восстановить ответ:
    • идти по строке first блоками по 8 символов;
    • для каждого блока посчитать его значение как двоичное число;
    • преобразовать число в символ ASCII и добавить в ответ.
  4. Вывести ответ.

4. Почему это работает

На втором этапе кодирования каждый символ строки из '0' и '1' был заменён на свой ASCII-код длины 8.

ASCII-коды символов '0' и '1' однозначны:

  • '0' это 00110000,
  • '1' это 00110001.

Поэтому, если разбить итоговую строку на 8-битные куски, каждый кусок однозначно восстанавливает один символ промежуточной строки. После обработки всех блоков мы точно получаем строку, которая была после первого этапа кодирования.

Далее эта промежуточная строка была получена из исходного текста стандартным способом: каждый символ заменили его 8-битным ASCII-кодом. Значит, если снова разбить её на блоки по 8 бит и перевести каждый блок из двоичной записи в число, а число — в символ ASCII, мы восстановим исходное сообщение.

Таким образом, алгоритм выполняет точное обратное преобразование к двум этапам кодирования, значит ответ будет правильным.


5. Сложность

Пусть длина входной строки равна n.

  • Первый проход по строке обрабатывает все символы один раз.
  • Второй проход тоже обрабатывает все символы один раз.

Итоговая сложность: O(n).

Дополнительная память: O(n), так как хранится промежуточная строка.


6. Код на C++17

#include <iostream>
#include <string>
#include <cstdio>

#if defined(_WIN32)
#include <io.h>
#include <fcntl.h>
#endif

using namespace std;

int main() {
#if defined(_WIN32)
    // Ensure raw bytes are written without CRLF translation.
    _setmode(_fileno(stdout), _O_BINARY);
#endif

    string s;
    cin >> s;

    string first;
    for (int i = 0; i < (int)s.size(); i += 8) {
        string block = s.substr(i, 8);
        if (block == "00110000") {
            first += '0';
        } else if (block == "00110001") {
            first += '1';
        }
    }

    string answer;
    for (int i = 0; i < (int)first.size(); i += 8) {
        int value = 0;
        for (int j = 0; j < 8; ++j) {
            value = value * 2 + (first[i + j] - '0');
        }
        answer += char(value);
    }

    fwrite(answer.data(), 1, answer.size(), stdout);
    fflush(stdout);
    return 0;
}

Важно: ответ — это произвольные байты (значения 0..255), среди которых могут встречаться \n (0x0A) и непечатаемые символы. Поэтому вывод выполняется именно как «сырые» байты (fwrite + бинарный режим stdout на Windows), а не через текстовый поток, который мог бы преобразовать перевод строки в \r\n.


7. Код на Python 3

import sys

s = input().strip()

first = []
for i in range(0, len(s), 8):
    block = s[i:i + 8]
    if block == "00110000":
        first.append('0')
    elif block == "00110001":
        first.append('1')

first = ''.join(first)

answer = []
for i in range(0, len(first), 8):
    block = first[i:i + 8]
    value = 0
    for ch in block:
        value = value * 2 + (ord(ch) - ord('0'))
    answer.append(value)

sys.stdout.buffer.write(bytes(answer))

Здесь ответ собирается как список целых 0..255 и пишется в sys.stdout.buffer как «сырые» байты, чтобы значения >= 128 не искажались текстовой кодировкой консоли (иначе, например, байт 0xFF вышел бы как два байта в UTF-8).


Комментарии

Еще нет ни одного комментария.