Блог пользователя Lapoux

Автор Lapoux, история, 3 месяца назад, По-русски

Некоторые новички могли после первого изучения std::stack в С++ начать писать его вместо std::vector, но это заблуждение и std::vector оптимальнее std::stack

  1. Оверхед. По умолчанию он использует deque, а не vector. Зачем вам лишние аллокации и фрагментация памяти, когда vector делает push_back и pop_back за O(1) с минимальным оверхедом?

  2. Бесполезные ограничения. Вы не можете заглянуть внутрь, кроме top(). А если вам нужен предпоследний элемент? С vector вы просто делаете v[v.size()-2] и всё. С stack вы вынуждены сначало удалять последний элемент при этом запоминая его а потом возварщать его обратно чтоб взять например предпоследний элемент.

  3. Никакой итерации. Хотите пройтись по элементам? Забудьте. А с vector вы можете спокойно пройтись по элементам.

  4. vector быстрее по скорости чем stack

  • Вот сравнение std::vector и std::stack по скорости

Вот код для стека

#include <iostream> 
#include <stack>

int main() {
    int n = 1e7;
    std::stack <int> st;

    for (int i = 0; i < n; ++i) {
        st.push(i);
    }

    long long sum = 0;
    while (!st.empty()) {
        sum += st.top();
        st.pop();
    }

    std::cout << sum;
}

Вот код с вектором

#include <iostream>
#include <vector>

int main() {
    int n = 1e7;
    std::vector <int> v;

    for (int i = 0; i < n; ++i) {
        v.push_back(i);
    }

    long long sum = 0;
    for (auto& i : v) {
        sum += i;
    }

    std::cout << sum;
}
  • Первый Код работает на моём компьютере за 0.183 секунды а второй за 0.109, что значительно лудше посравнению с результатом стэка.

Вывод: почти всегда std::vector будет лудше чем std::stack. Поэтому не советую его использовать, потому что ещё и с std::vector вы получите больший функционал.

Полный текст и комментарии »

  • Проголосовать: нравится
  • +10
  • Проголосовать: не нравится

Автор Lapoux, история, 3 месяца назад, По-английски

What is a Gray Code? - It is a sequence of numbers from 0 to 2 ^ n — 1 (^ — exponentiation), in which every next number differs from the previous one in exactly one binary bit. For example, for n = 3. 000, 001, 011, 010, 110, 111, 101, 100

Notice: each transition changes only one bit. Such order is often used when you need to iterate over all subsets and quickly update the current state (for example, sum or product) when adding or removing one element.

How to construct a Gray Code? The simplest way is recursive construction (reflected Gray code):

For n = 1: 0, 1.

For larger n we will do it recursively.

n: take the sequence for n — 1, to the right of it write the same sequence but reversed, and for each number in the right part add a new most significant bit.

  • In practice, the formula for direct computation of the i-th number in Gray code is more often used: g(i) = i ⊕ (i ≫ 1) where ⊕ is bitwise XOR, and >> is right shift. This works in O(1) and allows you to get the desired order without building a table.

  • If you need to restore the original index i by the Gray code value g, then the inverse transformation is used:


int rev_gray(int g) { int i = 0; while (g) { i ^= g; g >>= 1; } return i; }

One of the properties of Gray code: Take any permutation of numbers from 0 to 2 ^ n — 1 and calculate the sum of pairwise bitwise XOR between neighboring elements: S = (p0 ⊕ p1) + (p1 ⊕ p2) + ... + (p(2 ^ n − 2) ⊕ p(2 ^ n — 1)) (here ⊕ is bitwise XOR)

So, Gray code gives the minimum possible value of this sum among all permutations. Any other order of traversal will give a larger sum.

Also, Gray code has cyclicity, that is, if you connect the last and first elements, they will also differ in only 1 bit.

Where can this be useful?

  • If you are solving a problem where you need to traverse all masks, and the cost of transition from one mask to another is exactly the XOR of their values (or monotonically depends on XOR), then Gray code gives the minimum total cost.

  • This can be useful in problems where states are represented by bit strings, and the switching time between them is proportional to the number of changed bits, but with different weights (powers of two).

Полный текст и комментарии »

  • Проголосовать: нравится
  • +3
  • Проголосовать: не нравится

Автор Lapoux, история, 3 месяца назад, По-русски

Что такое код Грея? Это последовательность чисел от 0 до 2 ^ n — 1 (^ — возведение в степень), в которой каждое следующее число отличается от предыдущего ровно в одном двоичном разряде (одном бите). Например, для n = 3. 000, 001, 011, 010, 110, 111, 101, 100

  • Обратите внимание: каждый переход меняет только один бит. Такой порядок часто используют, когда нужно перебрать все подмножества и при этом быстро обновлять текущее состояние (например, сумму или произведение) при добавлении/удалении одного элемента.

Как построить код Грея? Самый простой способ – рекурсивное построение (отражённый код Грея):

  • Для n = 1 n = 1: 0, 1.

Для n больших будем делать рекурсивно. 1. n: берём последовательность для n — 1, справо от от неё записываем такую последовательность но развёрнутую и при этом для каждого числа в правой части добовляем новый старший бит. На практике же чаще используют формулу для прямого вычисления i-го числа в коде Грея: g(i) = i ⊕ (i ≫ 1) где ⊕ – побитовое исключающее ИЛИ, а >> – сдвиг вправо. Это работает за O(1) и позволяет получить нужный порядок без построения таблицы.

Если же нужно восстановить исходный номер i по значению кода Грея g, то используется обратное преобразование:

int rev_gray(int g) {
    int i = 0;
    while (g) {
        i ^= g;
        g >>= 1;
    }
    return i;
}
  • Одно из свойст Кода Грея: Возьмём любую перестановку чисел от 0 до 2 ^ n — 1 и посчитаем сумму попарных побитовых XOR между соседними элементами: S = (p0 ⊕ p1) + (p1 ⊕ p2) + ... + (p(2 ^ n − 2) ⊕ p(2 ^ n — 1)) (здесь ⊕ – побитовое XOR)
  • Так вот, код Грея даёт минимально возможное значение этой суммы среди всех перестановок. Любой другой порядок обхода даст сумму больше.
  • Так же код грея обладает цикличностью то есть если соеденить последний и первый элемент, они тоже будут отличатся только в 1 бите.

Где это может пригодиться?

  • Если вы решаете задачу, где нужно обойти все маски, а стоимость перехода из одной маски в другую равна именно XOR их значений (или монотонно зависит от XOR), то код Грея даст минимальную суммарную стоимость.

  • Это может быть полезно в задачах, где состояния представляются битовыми строками, а время переключения между ними пропорционально числу изменяемых битов, но с разными весами (степенями двойки).

Полный текст и комментарии »

  • Проголосовать: нравится
  • +24
  • Проголосовать: не нравится

Автор Lapoux, история, 3 месяца назад, По-русски

Привет, Codeforces!

  • Хочу поделиться короткой фишкой из C++17, о которой многие до сих пор забывают, хотя она делает олимпиадный код в разы аккуратнее.
  • Так можно делать абсолютно со всеми структурами.
  • Eсли раньше вы писали

struct edg {
    int fr, sc;
};

int main() {
    vector <edg> edges;
    
    ...
    
    for (int i = 0; i < (int) edges.size(); ++i) {
        int fr = edges[i].fr;
        int sc = edges[i].sc;
        
        ...
    }
}
  • То начиная с С++17 можно писать так.
struct edg {
    int fr, sc;
};

int main() {
    vector <edg> edges;
    
    ...
    
    for (auto& [fr, sc] : edges) {
        ...
    }
}

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

Полный текст и комментарии »

  • Проголосовать: нравится
  • +1
  • Проголосовать: не нравится

Автор Lapoux, история, 3 месяца назад, По-русски

Как спортивное програмирование помогает развивать мозг, и соответствено причины ей заниматся.

1) Спортивное програмирование развивает способность размышлять самому с собой и конструктивно искать где может не работать тот или иной код. Что способствует развитию возможности перебирать возможные исходы, что бывает полезно.

2) Часто приходится искать какие то лазейки в задачах, или замечать какие то тонкости, что в свою очередь развивает обращение внимания к мелочам

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

4) Так же спортивное програмирование, так же и задачи на кодефорсес, развивают твою скорость мышления, что в свою очередь достаточно полезно в реальной жизни.

Надеюсь вам понравились мои мысли о спортивном програмирование.

Полный текст и комментарии »

  • Проголосовать: нравится
  • -13
  • Проголосовать: не нравится

Автор Lapoux, история, 3 месяца назад, По-русски

Ещё не давно я поехал в летнюю компьютерную школу.

  • Но уже смена подходит к концу. За эту смену я узнал некоторые новые алгоритмы. Поел страйпсы чупа чупс. Поиграл в Футбол с моими друганами. Ещё участвовали в соревнованиях по баскетболу, билиарду.
  • Желаю тоже вам туда сьездить!

Полный текст и комментарии »

  • Проголосовать: нравится
  • +15
  • Проголосовать: не нравится