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

// Merlin helper agent for the Dabius.ai maze problem.
// The generator makes a perfect maze on odd grid cells: every room (odd,odd)
// is open, and each possible corridor between neighbouring rooms is one edge
// of a spanning tree.  We therefore only query those corridor cells.

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 a, b;       // room-graph endpoints, 0-based: r*m+c
    int qr, qc;     // 1-based maze cell to query: the corridor between them
    int X, Y;       // doubled midpoint coordinates in room grid, for ordering
    int dir;        // 0 vertical, 1 horizontal
};

static const string ALPH = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+/";

int enc6(int x) { return ALPH[x & 63]; }
int dec6(char ch) {
    if ('0' <= ch && ch <= '9') return ch - '0';
    if ('A' <= ch && ch <= 'Z') return 10 + ch - 'A';
    if ('a' <= ch && ch <= 'z') return 36 + ch - 'a';
    if (ch == '+') return 62;
    if (ch == '/') return 63;
    return 0;
}

vector<Edge> build_order(int N) {
    int m = (N + 1) / 2;
    vector<Edge> edges;
    edges.reserve(2 * m * max(0, m - 1));
    for (int r = 0; r < m; ++r) {
        for (int c = 0; c < m; ++c) {
            int v = r * m + c;
            if (r + 1 < m) {
                Edge e;
                e.a = v; e.b = v + m;
                e.qr = 2 * r + 2;       // between grid rows 2r+1 and 2r+3
                e.qc = 2 * c + 1;
                e.X = 2 * r + 1; e.Y = 2 * c;
                e.dir = 0;
                edges.push_back(e);
            }
            if (c + 1 < m) {
                Edge e;
                e.a = v; e.b = v + 1;
                e.qr = 2 * r + 1;
                e.qc = 2 * c + 2;       // between grid cols 2c+1 and 2c+3
                e.X = 2 * r; e.Y = 2 * c + 1;
                e.dir = 1;
                edges.push_back(e);
            }
        }
    }

    // Query likely solution-path edges early.  For this Kruskal grid maze the
    // corner-to-corner path is strongly biased toward the main diagonal.  Sorting
    // by diagonal band gives much earlier valid paths on average, while still
    // eventually scanning every edge for a hard guarantee.
    sort(edges.begin(), edges.end(), [m](const Edge& u, const Edge& v) {
        int target = 2 * (m - 1);
        int du = abs(u.X - u.Y), dv = abs(v.X - v.Y);
        if (du != dv) return du < dv;
        int cu = abs((u.X + u.Y) - target);
        int cv = abs((v.X + v.Y) - target);
        if (cu != cv) return cu < cv;
        int su = u.X + u.Y, sv = v.X + v.Y;
        if (su != sv) return su < sv;
        if (u.X != v.X) return u.X < v.X;
        if (u.Y != v.Y) return u.Y < v.Y;
        if (u.dir != v.dir) return u.dir < v.dir;
        if (u.a != v.a) return u.a < v.a;
        return u.b < v.b;
    });
    return edges;
}

int assigned_count(int totalEdges, int who, int activeAgents) {
    if (who >= activeAgents || who >= totalEdges) return 0;
    return (totalEdges - 1 - who) / activeAgents + 1;
}

string make_batch_body(const vector<unsigned char>& bits, int maxMsgLen) {
    int cnt = (int)bits.size();
    if (maxMsgLen < 4) {
        // Degenerate fallback for tiny message limits: one bit per message.
        return bits.empty() ? string("0") : string(1, bits[0] ? '1' : '0');
    }
    string body;
    body.reserve(3 + (cnt + 5) / 6);
    body.push_back('D');
    body.push_back((char)enc6(cnt >> 6));
    body.push_back((char)enc6(cnt & 63));
    for (int i = 0; i < cnt; i += 6) {
        int x = 0;
        for (int j = 0; j < 6 && i + j < cnt; ++j) {
            if (bits[i + j]) x |= (1 << j);
        }
        body.push_back((char)enc6(x));
    }
    return body;
}

char room_move(int from, int to, int m) {
    if (to == from + 1) return 'R';
    if (to == from - 1) return 'L';
    if (to == from + m) return 'D';
    if (to == from - m) return 'U';
    return '?';
}
char opposite_move(char ch) {
    if (ch == 'R') return 'L';
    if (ch == 'L') return 'R';
    if (ch == 'D') return 'U';
    return 'D';
}

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

    int N, NUM_AGENTS, ID, MAX_MSG_LEN;
    if (!(cin >> N >> NUM_AGENTS >> ID >> MAX_MSG_LEN)) return 0;
    string startup_rest;
    getline(cin, startup_rest); // consume the end of the startup line before using getline replies

    int m = (N + 1) / 2;
    int V = m * m;

    if (N == 1) {
        if (ID == 0) {
            cout << "!" << '\n' << flush;
        } else {
            cout << "halt" << '\n' << flush;
        }
        return 0;
    }

    vector<Edge> edges = build_order(N);
    int E = (int)edges.size();

    // Using every available agent is best on large finals, but on small mazes
    // too many workers produce too many tiny final messages.  sqrt(E) is a good
    // crossover; for N=501 this still uses all 50 agents.
    int activeAgents = NUM_AGENTS;
    if (E > 0) {
        int s = max(1, (int)floor(sqrt((double)E) + 0.5));
        activeAgents = min(NUM_AGENTS, max(1, s));
    }

    // With a zero-length message limit no worker can transmit even one bit,
    // so agent 0 falls back to a solo scan.  Finals use 256.
    if (MAX_MSG_LEN < 1) activeAgents = 1;

    if (ID >= activeAgents) {
        cout << "halt" << '\n' << flush;
        return 0;
    }

    int capacityBits;
    if (MAX_MSG_LEN >= 4) capacityBits = (MAX_MSG_LEN - 3) * 6;
    else capacityBits = 1;
    int targetBatch = max(16, (int)floor(sqrt((double)max(1, E)) + 0.5));
    int BATCH = max(1, min(capacityBits, targetBatch));
    int READ_PERIOD = max(8, BATCH / 2);

    auto send_batch_to_zero = [&](vector<unsigned char>& bits) {
        if (bits.empty()) return;
        string body = make_batch_body(bits, MAX_MSG_LEN);
        cout << "> 0 " << body << '\n' << flush;
        string ok;
        if (!getline(cin, ok)) exit(0);
        bits.clear();
    };

    if (ID != 0) {
        vector<unsigned char> bits;
        bits.reserve(BATCH);
        for (int pos = ID; pos < E; pos += activeAgents) {
            const Edge& e = edges[pos];
            cout << "? " << e.qr << ' ' << e.qc << '\n' << flush;
            string reply;
            if (!getline(cin, reply)) return 0;
            bits.push_back((!reply.empty() && reply[0] == '1') ? 1 : 0);
            if ((int)bits.size() >= BATCH) send_batch_to_zero(bits);
        }
        send_batch_to_zero(bits);
        cout << "halt" << '\n' << flush;
        return 0;
    }

    // Agent 0: collect packed edge results, maintain the known open forest, and
    // claim immediately once known open edges connect start to finish.
    DSU dsu(V);
    vector<signed char> known(E, -1);     // -1 unknown, 0 wall, 1 open
    vector<int> got(activeAgents, 0), total(activeAgents, 0);
    for (int a = 0; a < activeAgents; ++a) total[a] = assigned_count(E, a, activeAgents);

    vector<vector<pair<int,char>>> adj(V);
    bool connected = false;

    auto mark_edge = [&](int pos, bool open) {
        if (pos < 0 || pos >= E) return;
        if (known[pos] != -1) return;
        known[pos] = open ? 1 : 0;
        if (!open) return;
        const Edge& e = edges[pos];
        char ch = room_move(e.a, e.b, m);
        adj[e.a].push_back({e.b, ch});
        adj[e.b].push_back({e.a, opposite_move(ch)});
        dsu.unite(e.a, e.b);
        if (dsu.find(0) == dsu.find(V - 1)) connected = true;
    };

    auto decode_message = [&](int sender, const string& body) {
        if (sender <= 0 || sender >= activeAgents) return;
        if (body.empty()) return;
        if (body[0] == 'D' && (int)body.size() >= 3) {
            int cnt = dec6(body[1]) * 64 + dec6(body[2]);
            int base = got[sender];
            for (int i = 0; i < cnt; ++i) {
                int chIndex = 3 + i / 6;
                if (chIndex >= (int)body.size()) break;
                int x = dec6(body[chIndex]);
                bool bit = ((x >> (i % 6)) & 1) != 0;
                int pos = sender + (base + i) * activeAgents;
                mark_edge(pos, bit);
            }
            got[sender] += cnt;
            if (got[sender] > total[sender]) got[sender] = total[sender];
        } else if (body[0] == '0' || body[0] == '1') {
            int base = got[sender];
            int pos = sender + base * activeAgents;
            mark_edge(pos, body[0] == '1');
            got[sender]++;
            if (got[sender] > total[sender]) got[sender] = total[sender];
        }
    };

    auto claim_and_exit = [&]() {
        vector<int> parent(V, -1);
        vector<char> pmove(V, 0);
        deque<int> q;
        parent[0] = 0;
        q.push_back(0);
        while (!q.empty() && parent[V - 1] == -1) {
            int v = q.front(); q.pop_front();
            for (auto [to, ch] : adj[v]) {
                if (parent[to] == -1) {
                    parent[to] = v;
                    pmove[to] = ch;
                    q.push_back(to);
                    if (to == V - 1) break;
                }
            }
        }
        if (parent[V - 1] == -1) return; // Should not happen if DSU says connected.
        string roomPath;
        for (int cur = V - 1; cur != 0; cur = parent[cur]) roomPath.push_back(pmove[cur]);
        reverse(roomPath.begin(), roomPath.end());
        string path;
        path.reserve(roomPath.size() * 2);
        for (char ch : roomPath) {
            path.push_back(ch);
            path.push_back(ch);
        }
        cout << "! " << path << '\n' << flush;
        exit(0);
    };

    auto all_received = [&]() {
        for (int a = 0; a < activeAgents; ++a) if (got[a] < total[a]) return false;
        return true;
    };

    auto drain_messages = [&]() {
        while (true) {
            cout << "< ?" << '\n' << flush;
            string reply;
            if (!getline(cin, reply)) exit(0);
            if (reply == "- -") break;
            size_t sp = reply.find(' ');
            if (sp == string::npos) continue;
            int sender = atoi(reply.substr(0, sp).c_str());
            string body = reply.substr(sp + 1);
            decode_message(sender, body);
            if (connected) claim_and_exit();
        }
    };

    int ownQueriesSinceRead = 0;
    int ownDone = 0;
    for (int pos = 0; ; ) {
        if (connected) claim_and_exit();

        if (pos < E) {
            const Edge& e = edges[pos];
            cout << "? " << e.qr << ' ' << e.qc << '\n' << flush;
            string reply;
            if (!getline(cin, reply)) return 0;
            mark_edge(pos, (!reply.empty() && reply[0] == '1'));
            got[0]++;
            ownQueriesSinceRead++;
            pos += activeAgents;

            if (connected) claim_and_exit();
            if (ownQueriesSinceRead >= READ_PERIOD) {
                ownQueriesSinceRead = 0;
                drain_messages();
            }
        } else {
            if (!ownDone) ownDone = 1;
            if (connected) claim_and_exit();
            if (all_received()) {
                // In a valid maze this should imply connected; try to claim if so,
                // otherwise keep draining rather than risking an invalid path.
                if (dsu.find(0) == dsu.find(V - 1)) {
                    connected = true;
                    claim_and_exit();
                }
            }
            drain_messages();
        }
    }
}
