0

Given an array A of n integers and given queries in the form of range [l , r] and a value x, find the minimum of A[i] XOR x where l <= i <= r and x will be different for different queries.

I tried solving this problem using segment trees but I am not sure what type of information I should store in them as x will be different for different queries.

0 < number of queries <= 1e4

0 < n <= 1e4 
sam
  • 13
  • 4

4 Answers4

0

To solve this I used a std::vector as basis (not an array, or std::array), just for flexibility.

#include <algorithm>
#include <stdexcept>
#include <vector>

int get_xored_max(const std::vector<int>& values, const size_t l, const size_t r, const int xor_value)
{
    // check bounds of l and r
    if ((l >= values.size()) || (r >= values.size()))
    {
        throw std::invalid_argument("index out of bounds");
    }

    // todo check l < r

    // create left & right iterators to create a smaller vector 
    // only containing the subset we're interested in.
    auto left = values.begin() + l;
    auto right = values.begin() + r + 1;
    std::vector<int> range{ left, right };

    // xor all the values in the subset
    for (auto& v : range)
    {
        v ^= xor_value;
    }

    // use the standard library function for finding the iterator to the maximum
    // then use the * to dereference the iterator and get the value
    auto max_value = *std::max_element(range.begin(), range.end());
    return max_value;
}

int main()
{
    std::vector<int> values{ 1,3,5,4,2,4,7,9 };
    auto max_value = get_xored_max(values, 0u, 7u, 3);
    return 0;
}
Pepijn Kramer
  • 9,356
  • 2
  • 8
  • 19
  • I think the OP wants a more efficient solution instead of a naive brute force solution because he is thinking about using a segment tree. – TYeung Aug 14 '21 at 05:15
  • Could be :) I did not see any performance optimization requirements in his question. So if performance isn't a hard requirement I just settle for reusing as much standard library implementation as possible (and even then standard library containers can be pretty fast too). But if his assignment states he should use trees, then I stand corrected :) – Pepijn Kramer Aug 14 '21 at 05:31
  • thanks for the answer but as @LearningMathematics said I wanted an efficient solution and sorry I didn't specify the constraint. I will make an edit. – sam Aug 14 '21 at 06:46
  • Oh but that probably would mean repopulating the tree when a new x (xor value) is given. Which would be O(n). (sorting the tree again probably would be O(n log n)) So what should be more efficient? changing x, or searching sub-ranges [l,r]. In the end it might be that you have a tree for each x you've seen in the past, and building a new tree for each new x. Then you can efficiently search for [l,r] ranges in your trees – Pepijn Kramer Aug 14 '21 at 06:57
0

Approach - Trie + Offline Processing
Time Complexity - O(N32)
Space Complexity - O(N
32)

Edit:
This Approach will fail. I guess, we have to use square root decomposition instead of two pointers approach.

I have solved this problem using Trie for finding minimum xor in a range of [l,r]. I solved queries by offline processing by sorting them.

Input format:
the first line has n (no. of elements) and q (no. of queries). the second line has all n elements of the array. each subsequent line has a query and each query has 3 inputs l, r and x.

Example -
Input -

3 3
2 1 2
1 2 3
1 3 2
2 3 5

First, convert all 3 queries into queries sorted by l and r.
converted queries -

1 2 3
1 3 2
2 3 5

Key here is processing over sorted queries using two pointers approach.

#include <bits/stdc++.h>
using namespace std;

const int N = (int)2e4 + 77;

int n, q, l, r, x;
int a[N], ans[N];
vector<pair<pair<int, int>, pair<int, int>>> queries;

// Trie Implementation starts
struct node
{
  int nxt[2], cnt;
  void newnode()
  {
    memset(nxt, 0, sizeof(nxt));
    cnt = 0;
  }
} trie[N * 32];
int tot = 1;

void update(int x, int v)
{
  int p = 1;
  for (int i = 31; i >= 0; i--)
  {
    int id = x >> i & 1;
    if (!trie[p].nxt[id])
    {
      trie[++tot].newnode();
      trie[p].nxt[id] = tot;
    }
    p = trie[p].nxt[id];
    trie[p].cnt += v;
  }
}

int minXor(int x)
{
  int res = 0, p = 1;
  for (int i = 31; i >= 0; i--)
  {
    int id = x >> i & 1;
    if (trie[p].nxt[id] and trie[trie[p].nxt[id]].cnt)
      p = trie[p].nxt[id];
    else
    {
      p = trie[p].nxt[id ^ 1];
      res |= 1 << i;
    }
  }
  return res;
}
// Trie Implementation ends

int main()
{
  cin >> n >> q;
  for (int i = 1; i <= n; i += 1)
  {
    cin >> a[i];
  }
  for (int i = 1; i <= q; i += 1)
  {
    cin >> l >> r >> x;
    queries.push_back({{l, r}, {x, i}});
  }
  sort(queries.begin(), queries.end());
  int left = 1, right = 1;
  for (int i = 0; i < q; i += 1)
  {
    int l = queries[i].first.first;
    int r = queries[i].first.second;
    int x = queries[i].second.first;
    int index = queries[i].second.second;
    while (left < l)
    {
      update(a[left], -1);
      left += 1;
    }
    while (right <= r)
    {
      update(a[right], 1);
      right += 1;
    }
    ans[index] = minXor(x);
  }
  for (int i = 1; i <= q; i += 1)
  {
    cout << ans[i] << " \n";
  }
  return 0;
}
Gaurav Sharma
  • 573
  • 1
  • 4
  • 26
  • You can get O(n log n log M)-time construction and O(log n log M)-time queries (where M is an upper bound on the values) if you combine this idea with OP's idea of using a segment tree and build a trie for each segment. – David Eisenstat Aug 14 '21 at 16:33
0

Edit: with O(number of bits) code

Use a binary tree to store the values of A, look here : Minimum XOR for queries

What you need to change is adding to each node the range of indexes for A corresponding to the values in the leafs.

# minimal xor in a range
nbits=16    # Number of bits for numbers
asize=5000 # Array size
ntest=50     # Number of random test
from random import randrange

# Insert element a iindex iin the tree (increasing i only)
def tinsert(a,i,T):
for b in range(nbits-1,-1,-1):
    v=((a>>b)&1)
    T[v+2].append(i)
    if T[v]==[]:T[v]=[[],[],[],[]]
    T=T[v]
    
# Buildtree : builds a tree based on array V
def build(V):
T=[[],[],[],[]] # Init tree
for i,a in enumerate(V): tinsert(a,i,T)
return(T)

# Binary search : is T intersec [a,b] non empty ?
def binfind(T,a,b):
 s,e,om=0,len(T)-1,-1
 while True:
  m=(s+e)>>1
  v=T[m]
  if v<a: 
   s=m
   if m==om: return(a<=T[e]<=b)
  elif v>b: 
   e=m
   if m==om: return(a<=T[s]<=b)
  else: return(True) # a<=T(m)<=b
  om=m

# Look for the min xor in a give range index
def minx(x,s,e,T):
 if s<0 or s>=(len(T[2])+len(T[3])) or e<s: return
 r=0
 for b in range(nbits-1,-1,-1):
  v=((x>>b)&1)
  if T[v+2]==[] or not binfind(T[v+2],s,e): # not nr with b set to v ?
   v=1-v
  T=T[v]
   r=(r<<1)|v
 return(r)

# Tests the code on random arrays
max=(1<<nbits)-1
for i in range(ntest):
 A=[randrange(0,max) for i in range(asize)]
 T=build(A)
 x,s=randrange(0,max),randrange(0,asize-1)
 e=randrange(s,asize)
 if min(v^x for v in A[s:e+1])!=x^minx(x,s,e,T):
  print('error')
Jean Valj
  • 119
  • 4
0

I was able to solve this using segment tree and tries as suggested by @David Eisenstat

Below is an implementation in c++. I constructed a trie for each segment in the segment tree. And finding the minimum xor is just traversing and matching the corresponding trie using each bit of the query value (here)

#include <bits/stdc++.h>
#define rep(i, a, b) for (int i = a; i < b; i++)
using namespace std;

const int bits = 7;

struct trie {
    trie *children[2];
    bool end;
};

trie *getNode(void)
{
    trie *node        = new trie();
    node->end         = false;
    node->children[0] = NULL;
    node->children[1] = NULL;
    return node;
}

trie *merge(trie *l, trie *r)
{
    trie *node = getNode();
    // Binary 0:
    if (l->children[0] && r->children[0])
        node->children[0] = merge(l->children[0], r->children[0]);

    else if (!r->children[0])
        node->children[0] = l->children[0];

    else if (!l->children[0])
        node->children[0] = r->children[0];

    // Binary 1:
    if (l->children[1] && r->children[1])
        node->children[1] = merge(l->children[1], r->children[1]);

    else if (!r->children[1])
        node->children[1] = l->children[1];

    else if (!l->children[1])
        node->children[1] = r->children[1];

    return node;
}

void insert(trie *root, int num)
{
    int mask = 1 << bits;
    int bin;
    rep(i, 0, bits + 1)
    {
        bin = ((num & mask) >> (bits - i));
        if (!root->children[bin]) root->children[bin] = getNode();
        root = root->children[bin];
        mask = mask >> 1;
    }

    root->end = true;
}

struct _segTree {
    int n, height, size;
    vector<trie *> tree;

    _segTree(int _n)
    {
        n      = _n;
        height = (int)ceil(log2(n));
        size   = (int)(2 * pow(2, height) - 1);
        tree.resize(size);
    }

    trie *construct(vector<int> A, int start, int end, int idx)
    {
        if (start == end) {
            tree[idx] = getNode();
            insert(tree[idx], A[start]);
            return tree[idx];
        }

        int mid   = start + (end - start) / 2;
        tree[idx] = merge(construct(A, start, mid, 2 * idx + 1),
                          construct(A, mid + 1, end, 2 * idx + 2));
        return tree[idx];
    }

    int findMin(int num, trie *root)
    {
        int mask = 1 << bits;
        int bin;

        int rnum = 0;
        int res  = 0;

        rep(i, 0, bits + 1)
        {
            bin = ((num & mask) >> (bits - i));

            if (!root->children[bin]) {
                bin = 1 - bin;
                if (!root->children[bin]) return res ^ num;
            }

            rnum |= (bin << (bits - i));
            root = root->children[bin];
            if (root->end) res = rnum;
            mask = mask >> 1;
        }
        return res ^ num;
    }

    int Query(int X, int start, int end, int qstart, int qend, int idx)
    {
        if (qstart <= start && qend >= end) return findMin(X, tree[idx]);

        if (qstart > end || qend < start) return INT_MAX;

        int mid = start + (end - start) / 2;
        return min(Query(X, start, mid, qstart, qend, 2 * idx + 1),
                   Query(X, mid + 1, end, qstart, qend, 2 * idx + 2));
    }
};

int main()
{
    int n, q;
    vector<int> A;
    vector<int> L;
    vector<int> R;
    vector<int> X;

    cin >> n;
    A.resize(n, 0);

    rep(i, 0, n) cin >> A[i];

    cin >> q;
    L.resize(q);
    R.resize(q);
    X.resize(q);
    rep(i, 0, q) cin >> L[i] >> R[i] >> X[i];

    //---------------------code--------------------//

    _segTree segTree(n);
    segTree.construct(A, 0, n - 1, 0);

    rep(i, 0, q)
    {
        cout << segTree.Query(X[i], 0, n - 1, L[i], R[i], 0) << " ";
    }

    return 0;
}

Time complexity : O((2n - 1)*k + qklogn)

Space complexity : O((2n - 1)*2k)

k -> number of bits

sam
  • 13
  • 4