// agent — submission. Two strategies, dispatched by team size vs a maze-size
// threshold A* ~= 0.12*m^0.75 (the empirical v2/v3 crossover):
//
//   A <= A*  ->  v3 SPECULATIVE: every agent runs a *different* target-biased DFS
//               (forward from S / backward from T, varied tie-breaks). The run
//               ends at the first claim and all agents tick in lockstep, so the
//               score equals the FASTEST searcher -- i.e. min over A searches.
//               No messages; robust to agent deaths. Wins when the whole-tree
//               map cost 2T/A is high (few agents).
//   A >  A*  ->  v2 PARTITION/GATHER/SOLVE: split the m*m rooms into A ranges,
//               each agent probes its rooms' down+right passages, base64-streams
//               the edge bits to agent 0, which rebuilds the tree, BFS's the
//               unique path and claims it. Scales as ~2T/A; wins for many agents.
//
// Confirmed structure (merlin maze.rs): rooms at cells (2i+1,2j+1) are always
// empty; only passage cells are unknown and encode a spanning tree over the m*m
// room lattice. Each room step = two identical ULDR moves.
#include <bits/stdc++.h>
using namespace std;

int N, NUM_AGENTS, AGENT_ID, MAX_MSG_LEN, m;
long long T;

static string readLine() {
    string s; int c;
    while ((c = getchar()) != EOF && c != '\n') s.push_back((char)c);
    if (c == EOF && s.empty()) exit(0);
    return s;
}
static inline bool ask(int r, int c) {
    printf("? %d %d\n", r, c); fflush(stdout);
    return readLine() == "1";
}
static inline void sendMsg(int dst, const string &body) {
    printf("> %d %s\n", dst, body.c_str()); fflush(stdout);
    readLine();  // "OK"
}
static inline bool recvAny(int &sender, string &body) {
    printf("< ?\n"); fflush(stdout);
    string r = readLine();
    if (r == "- -") return false;
    size_t sp = r.find(' ');
    sender = stoi(r.substr(0, sp));
    body = (sp == string::npos) ? "" : r.substr(sp + 1);
    return true;
}

static const char *B64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
static string b64enc(const vector<uint8_t> &d) {
    string out; out.reserve((d.size() + 2) / 3 * 4);
    size_t i = 0;
    while (i + 3 <= d.size()) {
        uint32_t v = (d[i] << 16) | (d[i + 1] << 8) | d[i + 2];
        out.push_back(B64[(v >> 18) & 63]); out.push_back(B64[(v >> 12) & 63]);
        out.push_back(B64[(v >> 6) & 63]);  out.push_back(B64[v & 63]);
        i += 3;
    }
    if (d.size() - i == 1) {
        uint32_t v = d[i] << 16;
        out.push_back(B64[(v >> 18) & 63]); out.push_back(B64[(v >> 12) & 63]);
        out.push_back('='); out.push_back('=');
    } else if (d.size() - i == 2) {
        uint32_t v = (d[i] << 16) | (d[i + 1] << 8);
        out.push_back(B64[(v >> 18) & 63]); out.push_back(B64[(v >> 12) & 63]);
        out.push_back(B64[(v >> 6) & 63]);  out.push_back('=');
    }
    return out;
}
static vector<uint8_t> b64dec(const string &s) {
    static int inv[256]; static bool init = false;
    if (!init) { for (int i = 0; i < 256; i++) inv[i] = -1; for (int i = 0; i < 64; i++) inv[(unsigned char)B64[i]] = i; init = true; }
    vector<uint8_t> out; uint32_t buf = 0; int bits = 0;
    for (char c : s) {
        if (c == '=') break;
        int v = inv[(unsigned char)c];
        if (v < 0) continue;
        buf = (buf << 6) | v; bits += 6;
        if (bits >= 8) { bits -= 8; out.push_back((buf >> bits) & 0xFF); }
    }
    return out;
}
static inline void rangeOf(int k, int R, long long &lo, long long &hi) {
    lo = (long long)k * R; hi = min<long long>(T, lo + R);
    if (lo > T) lo = T;
}

static const int DI[4] = {1, 0, -1, 0};
static const int DJ[4] = {0, 1, 0, -1};
static const char DC[4] = {'D', 'R', 'U', 'L'};

static string dfs(int si, int sj, int gi, int gj, const int prio[4]) {
    vector<vector<char>> vis(m, vector<char>(m, 0));
    struct Frame { int i, j, next; };
    vector<Frame> st; string moves;
    vis[si][sj] = 1; st.push_back({si, sj, 0});
    while (!st.empty()) {
        Frame &f = st.back();
        if (f.i == gi && f.j == gj) break;
        if (f.next == 4) { st.pop_back(); if (!moves.empty()) moves.pop_back(); continue; }
        int d = prio[f.next++];
        int ni = f.i + DI[d], nj = f.j + DJ[d];
        if (ni < 0 || ni >= m || nj < 0 || nj >= m || vis[ni][nj]) continue;
        int pr = (2 * f.i + 1) + DI[d], pc = (2 * f.j + 1) + DJ[d];
        if (!ask(pr, pc)) continue;
        vis[ni][nj] = 1; moves.push_back(DC[d]); st.push_back({ni, nj, 0});
    }
    return moves;
}

// ---- v3 speculative: every agent races a distinct search; fastest claims. ----
static int run_speculative() {
    bool backward = (AGENT_ID & 1);
    int variant = (AGENT_ID / 2) & 3;
    static const int FWD[4][4] = {{0,1,2,3},{1,0,2,3},{0,1,3,2},{1,0,3,2}};
    static const int BWD[4][4] = {{2,3,0,1},{3,2,0,1},{2,3,1,0},{3,2,1,0}};
    const int *prio = backward ? BWD[variant] : FWD[variant];
    string rmoves, path;
    if (!backward) {
        rmoves = dfs(0, 0, m - 1, m - 1, prio);
        path.reserve(rmoves.size() * 2);
        for (char c : rmoves) { path.push_back(c); path.push_back(c); }
    } else {
        rmoves = dfs(m - 1, m - 1, 0, 0, prio);
        path.reserve(rmoves.size() * 2);
        for (int k = (int)rmoves.size() - 1; k >= 0; --k) {
            char c = rmoves[k];
            char iv = (c == 'D') ? 'U' : (c == 'U') ? 'D' : (c == 'R') ? 'L' : 'R';
            path.push_back(iv); path.push_back(iv);
        }
    }
    printf("! %s\n", path.c_str()); fflush(stdout);
    return 0;
}

// ---- v2 partition/gather/solve ----
static int run_parallel() {
    int R = (int)((T + NUM_AGENTS - 1) / NUM_AGENTS);
    long long lo, hi; rangeOf(AGENT_ID, R, lo, hi);
    vector<char> downOpen(T, 0), rightOpen(T, 0);
    for (long long idx = lo; idx < hi; ++idx) {
        int i = (int)(idx / m), j = (int)(idx % m);
        if (i + 1 < m) downOpen[idx] = ask(2 * i + 2, 2 * j + 1) ? 1 : 0;
        if (j + 1 < m) rightOpen[idx] = ask(2 * i + 1, 2 * j + 2) ? 1 : 0;
    }
    const int CHUNK = max(16, min(240, MAX_MSG_LEN - 12));

    if (AGENT_ID != 0) {
        long long cnt = hi - lo;
        if (cnt > 0) {
            long long nbits = 2 * cnt;
            vector<uint8_t> bytes((nbits + 7) / 8, 0);
            long long t = 0;
            for (long long idx = lo; idx < hi; ++idx) {
                if (downOpen[idx]) bytes[t >> 3] |= (1 << (t & 7)); ++t;
                if (rightOpen[idx]) bytes[t >> 3] |= (1 << (t & 7)); ++t;
            }
            string b64 = b64enc(bytes);
            int nchunks = (int)((b64.size() + CHUNK - 1) / CHUNK);
            if (nchunks == 0) nchunks = 1;
            for (int s = 0; s < nchunks; ++s)
                sendMsg(0, to_string(s) + " " + to_string(nchunks) + " " + b64.substr((size_t)s * CHUNK, CHUNK));
        }
        printf("halt\n"); fflush(stdout);
        return 0;
    }

    vector<int> expNchunks(NUM_AGENTS, -1), gotCount(NUM_AGENTS, 0);
    vector<vector<string>> pieces(NUM_AGENTS);
    vector<char> expected(NUM_AGENTS, 0), recovered(NUM_AGENTS, 0);
    int pendingSenders = 0;
    for (int k = 1; k < NUM_AGENTS; ++k) {
        long long klo, khi; rangeOf(k, R, klo, khi);
        if (khi > klo) { expected[k] = 1; ++pendingSenders; }
    }
    // Death tolerance: a live sender finishes probing (~2R turns) about when we do
    // and delivers immediately after, so waiting far past that means it died. On a
    // long idle stretch, self-probe the missing ranges instead of stalling to the
    // wall-clock deadline (which would score 0).
    const long long IDLE_LIMIT = (long long)R + 800;
    long long idle = 0;
    while (pendingSenders > 0) {
        int sender; string body;
        if (!recvAny(sender, body)) {
            if (++idle > IDLE_LIMIT) {
                for (int k = 1; k < NUM_AGENTS; ++k) {
                    if (!expected[k] || gotCount[k] == expNchunks[k]) continue;
                    long long klo, khi; rangeOf(k, R, klo, khi);
                    for (long long idx = klo; idx < khi; ++idx) {
                        int i = (int)(idx / m), j = (int)(idx % m);
                        if (i + 1 < m) downOpen[idx] = ask(2 * i + 2, 2 * j + 1) ? 1 : 0;
                        if (j + 1 < m) rightOpen[idx] = ask(2 * i + 1, 2 * j + 2) ? 1 : 0;
                    }
                    recovered[k] = 1; --pendingSenders;
                }
            }
            continue;
        }
        idle = 0;
        if (sender < 0 || sender >= NUM_AGENTS || !expected[sender]) continue;
        size_t p1 = body.find(' '), p2 = body.find(' ', p1 + 1);
        int seq = stoi(body.substr(0, p1));
        int nch = stoi(body.substr(p1 + 1, p2 - p1 - 1));
        string piece = body.substr(p2 + 1);
        if (expNchunks[sender] < 0) { expNchunks[sender] = nch; pieces[sender].assign(nch, string()); }
        if (seq >= 0 && seq < (int)pieces[sender].size() && pieces[sender][seq].empty() && nch != 0) {
            pieces[sender][seq] = piece;
            if (++gotCount[sender] == expNchunks[sender]) --pendingSenders;
        }
    }
    for (int k = 1; k < NUM_AGENTS; ++k) {
        if (!expected[k] || recovered[k]) continue;
        long long klo, khi; rangeOf(k, R, klo, khi);
        string b64; for (auto &pc : pieces[k]) b64 += pc;
        vector<uint8_t> bytes = b64dec(b64);
        long long t = 0;
        for (long long idx = klo; idx < khi; ++idx) {
            int b0 = (t >> 3) < (long long)bytes.size() ? ((bytes[t >> 3] >> (t & 7)) & 1) : 0; ++t;
            int b1 = (t >> 3) < (long long)bytes.size() ? ((bytes[t >> 3] >> (t & 7)) & 1) : 0; ++t;
            downOpen[idx] = (char)b0; rightOpen[idx] = (char)b1;
        }
    }
    vector<int> parent(T, -1); vector<char> pmove(T, 0), seen(T, 0);
    vector<int> q; q.reserve(T); q.push_back(0); seen[0] = 1;
    for (size_t h = 0; h < q.size(); ++h) {
        int idx = q[h];
        if (idx == T - 1) break;
        int i = idx / m, j = idx % m;
        if (i + 1 < m && downOpen[idx] && !seen[idx + m]) { seen[idx + m] = 1; parent[idx + m] = idx; pmove[idx + m] = 'D'; q.push_back(idx + m); }
        if (i - 1 >= 0 && downOpen[idx - m] && !seen[idx - m]) { seen[idx - m] = 1; parent[idx - m] = idx; pmove[idx - m] = 'U'; q.push_back(idx - m); }
        if (j + 1 < m && rightOpen[idx] && !seen[idx + 1]) { seen[idx + 1] = 1; parent[idx + 1] = idx; pmove[idx + 1] = 'R'; q.push_back(idx + 1); }
        if (j - 1 >= 0 && rightOpen[idx - 1] && !seen[idx - 1]) { seen[idx - 1] = 1; parent[idx - 1] = idx; pmove[idx - 1] = 'L'; q.push_back(idx - 1); }
    }
    string rmoves;
    for (int cur = (int)(T - 1); cur != 0; cur = parent[cur]) rmoves.push_back(pmove[cur]);
    reverse(rmoves.begin(), rmoves.end());
    string path; path.reserve(rmoves.size() * 2);
    for (char c : rmoves) { path.push_back(c); path.push_back(c); }
    printf("! %s\n", path.c_str()); fflush(stdout);
    return 0;
}

int main() {
    if (scanf("%d %d %d %d", &N, &NUM_AGENTS, &AGENT_ID, &MAX_MSG_LEN) != 4) return 0;
    { int ch; while ((ch = getchar()) != '\n' && ch != EOF) {} }
    m = (N + 1) / 2; T = (long long)m * m;

    if (m <= 1) { if (AGENT_ID == 0) printf("! \n"); else printf("halt\n"); fflush(stdout); return 0; }

    // v2/v3 crossover: v2 ~ 2T/A falls with A; v3 ~ f(m)*T is flat. Fitted the
    // crossover A* on the sim (m=26->~4, 151->~5, 251->~7). Bias is asymmetric:
    // wrongly using v2 at small A costs ~2x, wrongly using v3 at large A ~10%, so
    // this floor-fit leans slightly toward v3 in the tie region.
    int Astar = (int)(3.0 + 0.018 * (double)m);
    if (NUM_AGENTS <= Astar) return run_speculative();
    return run_parallel();
}
