Наименьший элемент после xor

Начиная с массива из N положительных целых чисел, поддержка запросов Q. Каждый запрос содержит положительное целое число i. Чтобы ответить на запрос, замените каждый элемент массива результатом его операции xoring на i, а затем выведите самый ранний индекс наименьшего элемента массива. Каждый запрос изменяет массив.

Я мог придумать только O(N*Q). Я пытаюсь подумать о структуре данных обновления диапазона, но xor может увеличить число, поэтому я не думаю, что это правильно.

Как решить быстрее?

#include <bits/stdc++.h>

using namespace std;

int main() {
    int N;
    cin >> N;
    vector<int> A(N);
    for (int i = 0; i < N; i++) {
        cin >> A[i];
    }

    int Q;
    cin >> Q;
    for (int j = 0; j < Q; j++) {
        int i;
        cin >> i;
        for (int k = 0; k < N; k++) {
            A[k] = A[k] ^ i;
        }
        int mini = 0;
        for (int k = 0; k < N; k++) {
            if (A[k] < A[mini]) {
                mini = k;
            }
        }
        cout << mini << '\n';
    }
    return 0;
}

Пример ввода

5
1 2 3 4 5
3
1
2
6

Пример вывода

0
2
4

Пример объяснения вывода

Первый запрос равен 1. После массива xor с 1 он становится 0 3 2 5 4. Наименьшее число: индекс 0.

Второй запрос равен 2. После операции xor предыдущего массива с 2 он становится 2 1 0 7 6. Наименьшее число: индекс 2.

Второй запрос равен 6. После операции xor предыдущего массива с 6 он становится 4 7 6 1 0. Наименьшее число: индекс 4.

cs.stackexchange.com лучше подходит для поиска алгоритмов.
3CxEZiVlQ 14.07.2024 00:12

@user24714692 user24714692 N и Q не более 10 ^ 5.

user25680598 14.07.2024 00:24

@3CxEZiVlQ ой, я ошибаюсь насчет того, для чего здесь нужен тег алгоритма? Я чувствую, что вы получите лучшие ответы на stackoverflow

user25680598 14.07.2024 00:26

Поместите все свои числа в суффиксное дерево. Это просто дерево, в котором путь через дерево (влево, влево, вправо,....) кодирует биты числа. Наименьшее число, как обычно, — это число, полученное путем постоянного движения налево. Xoring всего массива с запросом бита X означает, что на уровне X поддерево для 1 (или правого) становится наименьшим, а не 0. Вы можете решить поменять местами все узлы на этом уровне или изменить способ обхода дерева, чтобы xors во внимание. Это построение O(N log N) и поиск O(Q log N).

Botje 14.07.2024 00:42

Этот последний бит должен быть поиском O(Q), поскольку количество битов постоянно (но расплывчато).

Botje 14.07.2024 00:57

Существует возможное решение с пространством построения O(N×B), временем установки O(N×{B+logN}) и сложностью времени поиска O(Q). Где B=[log max(values)] или просто разрядность. Таким образом, общая сложность не будет превышать O(N×logN)+O(Q). Использование памяти является непрерывным; Таким образом, вероятность промахов в кэше для достаточно небольших наборов данных будет очень малой. Нет необходимости в экзотерических структурах данных.

Red.Wave 14.07.2024 10:19

@Red.Wave, можете ли вы объяснить свое решение? и что такое экзотерическая структура данных?

user25680598 18.07.2024 06:38

Если понимать описание буквально, избежать N*Q невозможно.

greybeard 18.07.2024 08:53

@ user25680598, в частности, я имею в виду никаких попыток. Это проще. Отсортируйте индекс и сохраните 2 индекса для каждого бита — первый 0 и первый 1. Затем сохраните накопленный xor всех запросов. Минимальное значение принадлежит этому более позднему списку.

Red.Wave 18.07.2024 15:26

@Red.Wave Я не слежу. минимальное значение принадлежит какому списку? можешь написать ответ? я уверен, что другим будет интересно более простое решение

user25680598 19.07.2024 00:06
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
10
167
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Вы можете использовать дерево сегментов, которое является распространенным методом запроса массивов и снижает временную сложность до O(N log N):

Ссылка


Код

#include <iostream>
#include <vector>

struct SegmentTree
{
    int N;
    std::vector<int> A;
    std::vector<int> seg;
    std::vector<std::pair<int, int>> tree;

    void build_tree(
        const int node,
        const int left,
        const int right)
    {
        if (left == right)
        {
            tree[node] = {A[left], left};
        }
        else
        {
            int mid = left + (right - left) / 2;
            build_tree(2 * node + 1, left, mid);
            build_tree(2 * node + 2, mid + 1, right);
            tree[node] = std::min(tree[2 * node + 1], tree[2 * node + 2]);
        }
    }

    void update_xor(
        const int node,
        const int left,
        const int right)
    {
        if (seg[node] != 0)
        {
            tree[node].first ^= seg[node];
            if (left != right)
            {
                seg[2 * node + 1] ^= seg[node];
                seg[2 * node + 2] ^= seg[node];
            }
            seg[node] = 0;
        }
    }

    void update_tree(
        const int l,
        const int r,
        const int val,
        const int node,
        const int left,
        const int right)
    {
        update_xor(node, left, right);
        if (left > right || left > r || right < l)
            return;
        if (left >= l && right <= r)
        {
            seg[node] ^= val;
            update_xor(node, left, right);
            return;
        }
        const int mid = left + (right - left) / 2;
        update_tree(l, r, val, 2 * node + 1, left, mid);
        update_tree(l, r, val, 2 * node + 2, mid + 1, right);
        tree[node] = std::min(tree[2 * node + 1], tree[2 * node + 2]);
    }

    std::pair<int, int> get_min(
        const int l,
        const int r,
        const int node,
        const int left,
        const int right)
    {
        update_xor(node, left, right);
        if (left > right || left > r || right < l)
            return {INT_MAX, -1};
        if (left >= l && right <= r)
            return tree[node];
        const int mid = left + (right - left) / 2;
        return std::min(
            get_min(l, r, 2 * node + 1, left, mid),
            get_min(l, r, 2 * node + 2, mid + 1, right));
    }

    SegmentTree(const std::vector<int> &nums) : A(nums)
    {
        N = std::size(A);
        tree.resize(4 * N);
        seg.resize(4 * N, 0);
        build_tree(0, 0, N - 1);
    }

    void update(const int l, const int r, const int val)
    {
        update_tree(l, r, val, 0, 0, N - 1);
    }

    std::pair<int, int> query(const int l, const int r)
    {
        return get_min(l, r, 0, 0, N - 1);
    }
};

int main()
{
    std::vector<int> A = {1, 2, 3, 4, 5};
    std::vector<int> Q = {1, 2, 6};
    for (auto &q : Q)
    {
        for (int i = 0; i < std::size(A); ++i)
            A[i] ^= q;
        SegmentTree seg_tree(A);
        std::pair<int, int> res = seg_tree.query(0, std::size(A) - 1);
        std::cout << res.second << "\n";
    }

    return 0;
}

Принты

0
2
4

Это тоже O(NQ) с еще худшим постоянным коэффициентом.

Unmitigated 14.07.2024 06:51

разве у вас все еще нет цикла N раз для каждого запроса? чем это лучше

user25680598 18.07.2024 06:39
Ответ принят как подходящий

Представляйте элементы массива как двоичные числа фиксированной ширины (т. е. последовательности битов 0 и 1), начиная со старшего бита. Создайте из них двоичное дерево, сохранив индекс массива в дереве внизу.

Если фиксированная ширина равна W, на любой отдельный запрос можно ответить за время O(W); при спуске по дереву, если есть одна ветвь, следуйте за ней, в противном случае перейдите по ветке «0», если значение xor имеет бит «0» в этой позиции, в противном случае перейдите к ветке «1».

Интуиция здесь проста; если в массиве есть хотя бы один элемент, имеющий тот же самый старший бит, что и значение xor, наименьший элемент в массиве должен быть одним из этих элементов. И так далее до следующего значимого бита. Trie делает поиск этих наборов эффективным.

Чтобы выполнить последовательные запросы, вместо обновления структуры данных выполните запрос к выполняющемуся XOR запросов на данный момент. Итак, если запросы Q1, Q2, Q3, Q4..., то вместо этого запросите Q1, Q1^Q2, Q1^Q2^Q3, Q1^Q2^Q3^Q4,... (где ^ — XOR). Это означает, что вам вообще не нужно обновлять дерево.

... и вместо обновления дерева для каждого запроса накапливайте запросы в обновления, которые применяются к остальным запросам.

Matt Timmermans 14.07.2024 18:26

@MattTimmermans ах да, я пропустил эту часть вопроса.

Paul Hankin 14.07.2024 19:30

что мне делать для обновления структуры данных?

user25680598 18.07.2024 06:38

@ user25680598 Мэтт ответил в комментарии, но я обновил свой ответ, чтобы сделать ответ более полным.

Paul Hankin 18.07.2024 08:07

Другие вопросы по теме