import std.algorithm;
import std.array;
import std.conv;
import std.math;
import std.range;
import std.stdio;
import std.string;
import std.typecons;
import std.container : DList;

T read(T)() { return readln.chomp.to!T; }
T[] reads(T)() { return readln.split.to!(T[]); }
alias readint = read!int;
alias readints = reads!int;

void main() {
    auto deque = Deque!int(400_000 * 3);
    int q = readint;
    for (int i = 0; i < q; i++) {
        auto xs = readints;
        switch (xs[0]) {
            case 0:
                if (xs[1] == 0) deque.insertFront(xs[2]);
                else deque.insertBack(xs[2]);
                break;
            case 1:
                writeln(deque[xs[1]]);
                break;
            case 2:
                if (xs[1] == 0) deque.removeFront();
                else deque.removeBack();
                break;
            default:
                break;
        }
    }
}

struct Deque(T) {
    private T[] _buffer;
    private int _front;
    private int _back;

    this(int size) {
        _buffer = new int[size];
        _front = _back = size / 2;
    }

    bool empty() @property { return  _front == _back; }
    int length() @property { return _back - _front; }

    void insertFront(T value) {
        if (empty) {
            _buffer[_front] = value;
            _back++;
            return;
        }

        _buffer[--_front] = value;
    }

    void insertBack(T value) {
        if (empty) {
            _buffer[_front] = value;
            _back++;
            return;
        }

        _buffer[_back++] = value;
    }

    void removeFront() {
        assert(!empty);
        ++_front;
    }

    void removeBack() {
        assert(!empty);
        --_back;
    }

    T opIndex(int index) {
        return _buffer[_front + index];
    }
}

