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

struct DSU {
    vector<int> p, sz;
    DSU() {}
    DSU(int n) { init(n); }
    void init(int n) { p.resize(n); sz.assign(n,1); iota(p.begin(), p.end(), 0); }
    int find(int x) { while (p[x] != x) { p[x] = p[p[x]]; x = p[x]; } return x; }
    bool unite(int a, int b) {
        a = find(a); b = find(b);
        if (a == b) return false;
        if (sz[a] < sz[b]) swap(a,b);
        p[b] = a; sz[a] += sz[b];
        return true;
    }
};

struct Edge {
    int u, v;
    int qr, qc;      // original maze cell to query
    char dir;        // direction from u to v in the room graph: R or D
    int x2, y2;      // doubled midpoint coordinates in the room grid, for priority
};

static const string ALPH = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
int decTable[256];

string encode_bits(const vector<unsigned char>& bits) {
    string s;
    s.reserve((bits.size() + 5) / 6);
    for (size_t i = 0; i < bits.size(); i += 6) {
        int v = 0;
        for (int b = 0; b < 6; ++b) {
            if (i + b < bits.size() && bits[i + b]) v |= (1 << b);
        }
        s.push_back(ALPH[v]);
    }
    return s;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    for (int i = 0; i < 256; ++i) decTable[i] = -1;
    for (int i = 0; i < (int)ALPH.size(); ++i) decTable[(unsigned char)ALPH[i]] = i;

    int N, A, ID, MAX_MSG_LEN;
    if (!(cin >> N >> A >> ID >> MAX_MSG_LEN)) return 0;

    auto claim = [&](const string& path) -> void {
        if (path.empty()) cout << "!\n" << flush;
        else cout << "! " << path << '\n' << flush;
        exit(0);
    };
    auto halt = [&]() -> void {
        cout << "halt\n" << flush;
        exit(0);
    };

    if (N == 1) {
        if (ID == 0) claim("");
        else halt();
    }

    int m = (N + 1) / 2;              // number of rooms per side
    int V = m * m;
    vector<Edge> edges;
    edges.reserve(2 * m * max(0, m - 1));

    auto vid = [&](int i, int j) { return i * m + j; };
    for (int i = 0; i < m; ++i) {
        for (int j = 0; j < m; ++j) {
            int u = vid(i, j);
            if (j + 1 < m) {
                Edge e;
                e.u = u; e.v = vid(i, j + 1);
                e.qr = 2 * i + 1;        // corridor between odd/odd rooms
                e.qc = 2 * j + 2;
                e.dir = 'R';
                e.x2 = 2 * i;            // midpoint in doubled room coordinates: (i, j+0.5)
                e.y2 = 2 * j + 1;
                edges.push_back(e);
            }
            if (i + 1 < m) {
                Edge e;
                e.u = u; e.v = vid(i + 1, j);
                e.qr = 2 * i + 2;
                e.qc = 2 * j + 1;
                e.dir = 'D';
                e.x2 = 2 * i + 1;        // midpoint: (i+0.5, j)
                e.y2 = 2 * j;
                edges.push_back(e);
            }
        }
    }
    int E = (int)edges.size();

    // Global query order: diagonal bands first.  The random Kruskal path from
    // corner to corner is much more likely to stay near the main diagonal, and
    // we can claim as soon as the known-open edges connect the two corners.
    vector<vector<int>> buckets(2 * m + 3);
    for (int idx = 0; idx < E; ++idx) {
        int d = abs(edges[idx].x2 - edges[idx].y2);
        buckets[d].push_back(idx);
    }
    vector<int> order;
    order.reserve(E);
    for (auto &b : buckets) {
        for (int idx : b) order.push_back(idx);
    }

    auto local_count = [&](int agent) -> int {
        if (agent >= E) return 0;
        return (E - 1 - agent) / A + 1;
    };

    // Batch size in *queried edges*.  540 bits -> 90 printable chars with the
    // usual MAX_MSG_LEN=256.  If MAX_MSG_LEN changes, stay safely within it.
    int payloadChars = max(1, min(MAX_MSG_LEN, 90));
    int BATCH = 6 * payloadChars;

    auto ask_edge = [&](int eidx) -> bool {
        const Edge &e = edges[eidx];
        cout << "? " << e.qr << ' ' << e.qc << '\n' << flush;
        string rep;
        if (!(cin >> rep)) exit(0);
        return rep == "1";
    };

    if (ID != 0) {
        vector<unsigned char> bits;
        bits.reserve(BATCH);
        for (long long k = 0, pos = ID; pos < E; ++k, pos += A) {
            int eidx = order[(int)pos];
            bits.push_back((unsigned char)ask_edge(eidx));
            if ((int)bits.size() == BATCH) {
                string body = encode_bits(bits);
                cout << "> 0 " << body << '\n' << flush;
                string ok;
                if (!(cin >> ok)) return 0;
                bits.clear();
            }
        }
        if (!bits.empty()) {
            string body = encode_bits(bits);
            cout << "> 0 " << body << '\n' << flush;
            string ok;
            if (!(cin >> ok)) return 0;
        }
        halt();
    }

    // Agent 0 is the only coordinator/claimer.
    DSU dsu(V);
    vector<vector<pair<int,char>>> adj(V);
    vector<int> processed(A, 0), need(A, 0);
    for (int a = 0; a < A; ++a) need[a] = local_count(a);

    auto add_present_edge = [&](int eidx) {
        const Edge &e = edges[eidx];
        // A subset of a tree is a forest; still add the edge even if defensive
        // DSU says it was already connected (should not happen for real input).
        dsu.unite(e.u, e.v);
        if (e.dir == 'R') {
            adj[e.u].push_back({e.v, 'R'});
            adj[e.v].push_back({e.u, 'L'});
        } else {
            adj[e.u].push_back({e.v, 'D'});
            adj[e.v].push_back({e.u, 'U'});
        }
    };

    auto connected = [&]() -> bool {
        return dsu.find(0) == dsu.find(V - 1);
    };

    auto build_path = [&]() -> string {
        vector<int> par(V, -1);
        vector<char> pch(V, 0);
        deque<int> q;
        par[0] = -2;
        q.push_back(0);
        while (!q.empty() && par[V - 1] == -1) {
            int u = q.front(); q.pop_front();
            for (auto [v, ch] : adj[u]) {
                if (par[v] == -1) {
                    par[v] = u;
                    pch[v] = ch;
                    q.push_back(v);
                    if (v == V - 1) break;
                }
            }
        }
        string roomMoves;
        for (int cur = V - 1; cur != 0; cur = par[cur]) {
            if (cur < 0 || par[cur] == -1) return string(); // should never happen once connected
            roomMoves.push_back(pch[cur]);
        }
        reverse(roomMoves.begin(), roomMoves.end());
        string ans;
        ans.reserve(roomMoves.size() * 2);
        for (char ch : roomMoves) {
            ans.push_back(ch);
            ans.push_back(ch);
        }
        return ans;
    };

    auto process_bit = [&](int sender, bool bit) {
        if (sender < 0 || sender >= A) return;
        if (processed[sender] >= need[sender]) return;
        long long pos = (long long)sender + (long long)processed[sender] * A;
        if (pos < E && bit) add_present_edge(order[(int)pos]);
        processed[sender]++;
    };

    auto process_body = [&](int sender, const string& body) {
        for (unsigned char ch : body) {
            int val = decTable[ch];
            if (val < 0) continue;
            for (int b = 0; b < 6; ++b) {
                if (processed[sender] >= need[sender]) return;
                process_bit(sender, (val >> b) & 1);
            }
        }
    };

    auto all_done = [&]() -> bool {
        for (int a = 0; a < A; ++a) if (processed[a] < need[a]) return false;
        return true;
    };

    while (true) {
        // Query one own batch in the same diagonal-priority order.
        int did = 0;
        while (did < BATCH && processed[0] < need[0]) {
            long long pos = (long long)processed[0] * A; // sender 0
            bool bit = ask_edge(order[(int)pos]);
            process_bit(0, bit);
            ++did;
            if (connected()) claim(build_path());
        }

        // Then drain roughly one batch from everyone else.  The first receive
        // after a batch can legitimately miss because messages sent this turn
        // become visible only next turn; the extra attempt is intentional.
        int attempts = max(1, A);
        while (attempts-- > 0 && !all_done()) {
            cout << "< ?\n" << flush;
            string s, body;
            if (!(cin >> s >> body)) return 0;
            if (s != "-") {
                int sender = -1;
                try { sender = stoi(s); } catch (...) { sender = -1; }
                if (sender >= 0 && sender < A) {
                    process_body(sender, body);
                    if (connected()) claim(build_path());
                }
            }
        }

        if (all_done()) {
            // Full tree has been learned, so this must now be connected.
            if (connected()) claim(build_path());
            // Defensive fallback: should be unreachable for a valid referee.
            cout << ".\n" << flush;
            string ok; if (!(cin >> ok)) return 0;
        }
    }
}
