// Datacenter Imprisonment — cooperative maze-escape agent.
//
// The empty cells form a tree carved by the provided generator, which ALWAYS
// makes rooms at (odd,odd) empty and pillars at (even,even) walls; only the
// "between" cells (exactly one even coordinate) are unknown tree edges. And
// `? r c` may probe ANY cell (agents are not physical robots). So all K agents
// cooperatively scan the E = 2*m*(m-1) potential edges in parallel: each agent
// owns a disjoint, deterministic subset and probes one edge per lockstep turn.
// That makes the run take ~E/K turns instead of the tens of thousands a lone
// searcher needs (which timed out with many agents).
//
// Agent 0 is the assembler. Workers STREAM their probed results to it as they
// go (packed 6 bits/printable-char, full-length messages), so the data is ready
// by the time agent 0 needs it; agent 0 is given a slightly smaller probe shard
// to offset the reads it must perform, keeping its total work ~= E/K rather than
// E/K + gather. It rebuilds the spanning tree, BFS-solves the unique room path
// (0,0)->(m-1,m-1), and claims. If a worker dies before delivering its shard,
// agent 0 self-probes the missing edges, so a single dropout never stalls a run.

#include <bits/stdc++.h>
using namespace std;

static char linebuf[4096];

// Read one reply line from Merlin. Returns false on EOF (we were killed).
static bool readLine() {
    if (!fgets(linebuf, sizeof(linebuf), stdin)) return false;
    return true;
}

int main() {
    int N, K, ID, MAXMSG;
    if (scanf("%d %d %d %d", &N, &K, &ID, &MAXMSG) != 4) return 0;
    int ch;
    while ((ch = getchar()) != '\n' && ch != EOF) {} // consume rest of startup line

    if (N == 1) { // (1,1) == (N,N): empty path.
        if (ID == 0) { printf("!\n"); fflush(stdout); }
        else { printf("halt\n"); fflush(stdout); }
        return 0;
    }

    const int m = (N + 1) / 2;                    // rooms per side
    const long long H = (long long)m * (m - 1);   // horizontal edges
    const long long E = 2 * H;                    // total potential tree edges

    const int CHUNK = max(1, min(MAXMSG, 250));   // message payload chars
    const int BITS_PER_CHAR = 6;                  // pack 6 bits into printable chars 48..111

    auto edgeCell = [&](long long e, int &r, int &c) {
        if (e < H) { long long i = e / (m - 1), j = e % (m - 1); r = 2*i+1; c = 2*j+2; }
        else       { long long v = e - H; long long i = v / m, j = v % m; r = 2*i+2; c = 2*j+1; }
    };

    // Load-balanced ownership: base = E/K; agent 0 probes fewer edges (c0) to
    // offset the gather reads it must perform, and the freed residue-0 edges are
    // spread across the workers. Deterministic and identical on every agent.
    const long long base = E / K;
    const long long bitsPerMsg = (long long)CHUNK * BITS_PER_CHAR;
    const long long msgsPerWorker = (base + bitsPerMsg - 1) / bitsPerMsg;
    const long long G = (K > 1) ? (K - 1) * msgsPerWorker : 0;
    long long c0 = base - G; if (c0 < 0) c0 = 0;

    auto owner = [&](long long e) -> int {
        int a = (int)(e % K);
        if (a != 0) return a;
        long long t = e / K;              // index within residue-0 class
        if (t < c0) return 0;
        if (K == 1) return 0;
        return 1 + (int)((t - c0) % (K - 1));
    };

    if (ID != 0) {
        // ---- WORKER ----
        // Collect our edges (increasing index) and probe them, streaming results.
        string payload;
        int acc = 0, nbits = 0;
        bool any = false;
        for (long long e = 0; e < E; e++) {
            if (owner(e) != ID) continue;
            any = true;
            int r, c; edgeCell(e, r, c);
            printf("? %d %d\n", r, c); fflush(stdout);
            if (!readLine()) return 0;
            int bit = (linebuf[0] == '1') ? 1 : 0;
            acc |= (bit << nbits);
            if (++nbits == BITS_PER_CHAR) { payload.push_back((char)(48 + acc)); acc = 0; nbits = 0; }
            if ((int)payload.size() >= CHUNK) {   // stream a full chunk right away
                printf("> 0 %s\n", payload.c_str()); fflush(stdout);
                if (!readLine()) return 0;        // "OK"
                payload.clear();
            }
        }
        if (nbits > 0) payload.push_back((char)(48 + acc));
        if (!payload.empty()) {
            printf("> 0 %s\n", payload.c_str()); fflush(stdout);
            if (!readLine()) return 0;
        }
        (void)any;
        printf("halt\n"); fflush(stdout);
        return 0;
    }

    // ---- ASSEMBLER (agent 0) ----
    // Per-sender ordered edge lists (decode-by-iteration works for any owner()).
    vector<vector<int>> edgesOf(K);
    for (long long e = 0; e < E; e++) edgesOf[owner(e)].push_back((int)e);

    vector<char> present((size_t)E, 0);
    vector<char> known((size_t)E, 0);
    long long knownCount = 0;
    auto setEdge = [&](long long e, int bit) {
        if (!known[e]) { known[e] = 1; present[e] = (char)bit; knownCount++; }
    };

    // Probe our own (reduced) shard.
    for (int e : edgesOf[0]) {
        int r, c; edgeCell(e, r, c);
        printf("? %d %d\n", r, c); fflush(stdout);
        if (!readLine()) return 0;
        setEdge(e, (linebuf[0] == '1') ? 1 : 0);
    }

    vector<long long> got(K, 0);
    long long maxMsgs = 0;
    for (int a = 1; a < K; a++) {
        long long bits = (long long)edgesOf[a].size();
        long long chars = (bits + BITS_PER_CHAR - 1) / BITS_PER_CHAR;
        long long msgs = (chars + CHUNK - 1) / CHUNK;
        maxMsgs = max(maxMsgs, msgs);
    }
    const long long STUCK = 2 * maxMsgs + 5LL * K + 1000;

    long long consecutiveMiss = 0;
    while (knownCount < E && consecutiveMiss < STUCK) {
        printf("< ?\n"); fflush(stdout);
        if (!readLine()) return 0;
        if (linebuf[0] == '-') { consecutiveMiss++; continue; }
        consecutiveMiss = 0;
        int sender = 0; char *p = linebuf;
        while (*p >= '0' && *p <= '9') { sender = sender * 10 + (*p - '0'); p++; }
        if (*p == ' ') p++;
        char *end = p; while (*end && *end != '\n' && *end != '\r') end++;
        *end = '\0';
        if (sender <= 0 || sender >= K) continue;
        for (char *q = p; *q; q++) {
            int val = (int)(*q) - 48;
            for (int k = 0; k < BITS_PER_CHAR && got[sender] < (long long)edgesOf[sender].size(); k++) {
                int bit = (val >> k) & 1;
                setEdge(edgesOf[sender][got[sender]], bit);
                got[sender]++;
            }
        }
    }

    // Fallback: probe any edges still unknown (a worker must have dropped).
    for (long long e = 0; e < E; e++) {
        if (known[e]) continue;
        int r, c; edgeCell(e, r, c);
        printf("? %d %d\n", r, c); fflush(stdout);
        if (!readLine()) return 0;
        setEdge(e, (linebuf[0] == '1') ? 1 : 0);
    }

    // Build room-graph adjacency from present edges.
    vector<char> rightOpen((size_t)m * m, 0), downOpen((size_t)m * m, 0);
    for (long long e = 0; e < E; e++) {
        if (!present[e]) continue;
        if (e < H) { long long i = e / (m - 1), j = e % (m - 1); rightOpen[i * m + j] = 1; }
        else       { long long v = e - H; long long i = v / m, j = v % m; downOpen[i * m + j] = 1; }
    }

    // BFS the unique path (0,0) -> (m-1,m-1) in the room graph.
    const int total = m * m;
    vector<int> par(total, -1);
    vector<char> pdir(total, 0);
    vector<char> vis(total, 0);
    vector<int> q; q.reserve(total);
    int startId = 0, goalId = (m - 1) * m + (m - 1);
    vis[startId] = 1; q.push_back(startId);
    for (size_t head = 0; head < q.size(); head++) {
        int cur = q[head];
        if (cur == goalId) break;
        int i = cur / m, j = cur % m;
        if (j + 1 < m && rightOpen[cur] && !vis[i * m + (j + 1)]) {
            int nx = i * m + (j + 1); vis[nx] = 1; par[nx] = cur; pdir[nx] = 'R'; q.push_back(nx);
        }
        if (j > 0 && rightOpen[i * m + (j - 1)] && !vis[i * m + (j - 1)]) {
            int nx = i * m + (j - 1); vis[nx] = 1; par[nx] = cur; pdir[nx] = 'L'; q.push_back(nx);
        }
        if (i + 1 < m && downOpen[cur] && !vis[(i + 1) * m + j]) {
            int nx = (i + 1) * m + j; vis[nx] = 1; par[nx] = cur; pdir[nx] = 'D'; q.push_back(nx);
        }
        if (i > 0 && downOpen[(i - 1) * m + j] && !vis[(i - 1) * m + j]) {
            int nx = (i - 1) * m + j; vis[nx] = 1; par[nx] = cur; pdir[nx] = 'U'; q.push_back(nx);
        }
    }

    // Reconstruct room-step directions, then emit each move twice (2 cells/step).
    string dirs;
    for (int cur = goalId; cur != startId; cur = par[cur]) {
        if (par[cur] < 0) { dirs.clear(); break; }
        dirs.push_back(pdir[cur]);
    }
    reverse(dirs.begin(), dirs.end());
    string moves; moves.reserve(dirs.size() * 2);
    for (char d : dirs) { moves.push_back(d); moves.push_back(d); }

    printf("! %s\n", moves.c_str());
    fflush(stdout);
    return 0;
}
