One can solve this by using a merge sort related segment tree. At each node corresponding to the range [l, r], the array stored in this node will be the sorted array of A[l...r]. We can preprocess this in O(n log n) time and the space complexity will also be O(n log n) since each element appears only one time at each height of the tree, which is equal to O(log n).
The naive approach to build this tree is to sort the array at each node with an O(n log n) algorithm which gives you a total time complexity of O(n log^2 n). But I've already mentioned that this procedure looks like merge sort, so we can use the same procedure to obtain a time complexity of O(n log n) building time.
For example let's consider the first four elements of your example array [3, 6, 7, 1]. The tree I described will look like this:
[1,3,6,7]
/ \
[3,6] [1,7]
/ \ / \
[3] [6] [7] [1]
Now queries can be done in O(log^2 n) time if you binary search the elements at the corresponding node.
Building time: O(n log n)
Query time: O(log^2 n)
Edit (code for building the tree in C++, querying is left as an exercise):
#include <vector>
using namespace std;
const int MAX_N = 10000;
int A[MAX_N], N; // the array of the integers
vector<int> T[MAX_N * 4];
void merge(vector<int>& C, vector<int>& A, vector<int>& B){
int i = 0, j = 0, n = A.size(), m = B.size();
C.assign(n + m, 0);
while(i < n || j < m){
if(i == n) C[i + j] = B[j], j++;
else if(j == m) C[i + j] = A[i], i++;
else {
if(A[i] <= B[j]) {
C[i + j] = A[i];
i++;
} else {
C[i + j] = B[j];
j ++;
}
}
}
}
void build(int n, int L, int R){
if(L == R) T[n] = vector<int>(1, A[L]);
else {
build(n * 2, L, (L + R) / 2);
build(n * 2 + 1, (L + R) / 2 + 1, R);
merge(T[n], T[n * 2], T[n * 2 + 1]);
}
}
int main(){
N = 4;
A[0] = 3, A[1] = 6, A[2] = 7, A[3] = 1;
build(1, 0, N - 1);
return 0;
}
a[i]?