Начиная с массива из 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.
@user24714692 user24714692 N и Q не более 10 ^ 5.
@3CxEZiVlQ ой, я ошибаюсь насчет того, для чего здесь нужен тег алгоритма? Я чувствую, что вы получите лучшие ответы на stackoverflow
Поместите все свои числа в суффиксное дерево. Это просто дерево, в котором путь через дерево (влево, влево, вправо,....) кодирует биты числа. Наименьшее число, как обычно, — это число, полученное путем постоянного движения налево. Xoring всего массива с запросом бита X означает, что на уровне X поддерево для 1 (или правого) становится наименьшим, а не 0. Вы можете решить поменять местами все узлы на этом уровне или изменить способ обхода дерева, чтобы xors во внимание. Это построение O(N log N) и поиск O(Q log N).
Этот последний бит должен быть поиском O(Q), поскольку количество битов постоянно (но расплывчато).
Существует возможное решение с пространством построения O(N×B), временем установки O(N×{B+logN}) и сложностью времени поиска O(Q). Где B=[log max(values)] или просто разрядность. Таким образом, общая сложность не будет превышать O(N×logN)+O(Q). Использование памяти является непрерывным; Таким образом, вероятность промахов в кэше для достаточно небольших наборов данных будет очень малой. Нет необходимости в экзотерических структурах данных.
@Red.Wave, можете ли вы объяснить свое решение? и что такое экзотерическая структура данных?
Если понимать описание буквально, избежать N*Q невозможно.
@ user25680598, в частности, я имею в виду никаких попыток. Это проще. Отсортируйте индекс и сохраните 2 индекса для каждого бита — первый 0 и первый 1. Затем сохраните накопленный xor всех запросов. Минимальное значение принадлежит этому более позднему списку.
@Red.Wave Я не слежу. минимальное значение принадлежит какому списку? можешь написать ответ? я уверен, что другим будет интересно более простое решение
Вы можете использовать дерево сегментов, которое является распространенным методом запроса массивов и снижает временную сложность до 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)
с еще худшим постоянным коэффициентом.
разве у вас все еще нет цикла N раз для каждого запроса? чем это лучше
Представляйте элементы массива как двоичные числа фиксированной ширины (т. е. последовательности битов 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). Это означает, что вам вообще не нужно обновлять дерево.
... и вместо обновления дерева для каждого запроса накапливайте запросы в обновления, которые применяются к остальным запросам.
@MattTimmermans ах да, я пропустил эту часть вопроса.
что мне делать для обновления структуры данных?
@ user25680598 Мэтт ответил в комментарии, но я обновил свой ответ, чтобы сделать ответ более полным.