// 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 keeps a live partial graph and claims as
// soon as the discovered open edges connect start to finish. If that does not
// happen early, the protocol still degenerates to the old full-maze scan. If a
// worker dies before delivering its shard, agent 0 self-probes the missing edges.

#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 totalRooms = m * m;
    const int startId = 0, goalId = (m - 1) * m + (m - 1);

    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

    vector<int> scanEdge((size_t)E);
    iota(scanEdge.begin(), scanEdge.end(), 0);
    auto edgeKey = [&](int e) {
        long long x2, y2;
        if (e < H) {
            long long i = e / (m - 1), j = e % (m - 1);
            x2 = 2 * i;
            y2 = 2 * j + 1;
        } else {
            long long v = e - H, i = v / m, j = v % m;
            x2 = 2 * i + 1;
            y2 = 2 * j;
        }
        return tuple<long long, long long, long long, long long>(
            llabs(x2 - y2), x2 + y2, x2, y2
        );
    };
    sort(scanEdge.begin(), scanEdge.end(), [&](int a, int b) {
        return edgeKey(a) < edgeKey(b);
    });

    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; }
    };

    const long long bitsPerMsg = (long long)CHUNK * BITS_PER_CHAR;
    auto residueCount = [&](int a) -> long long {
        if (a >= E) return 0;
        return (E - 1 - a) / K + 1;
    };
    auto finishTurnForC0 = [&](long long c0) -> long long {
        if (K == 1) return E + 1;
        vector<long long> q(K, 0);
        q[0] = c0;
        long long count0 = residueCount(0);
        long long freed = count0 - c0;
        for (int a = 1; a < K; a++) {
            q[a] = residueCount(a) + freed / (K - 1) + ((a - 1) < freed % (K - 1));
        }
        vector<pair<long long, int>> ready;
        for (int a = 1; a < K; a++) {
            long long msgs = (q[a] + bitsPerMsg - 1) / bitsPerMsg;
            for (long long k = 1; k <= msgs; k++) {
                long long sent = min(k * bitsPerMsg, q[a]) + k;
                ready.push_back({sent + 1, a});
            }
        }
        sort(ready.begin(), ready.end());
        long long t = q[0] + 1;
        for (auto [r, a] : ready) {
            (void)a;
            if (t < r) t = r;
            t++;
        }
        return t;
    };

    // Ownership is residue based, but agent 0's residue-0 prefix is chosen by
    // exact schedule simulation instead of a coarse heuristic. The gain is only
    // a few turns, but this is deterministic and essentially free.
    long long c0 = (K == 1) ? E : 0;
    if (K > 1) {
        long long count0 = residueCount(0);
        long long bestFinish = LLONG_MAX;
        for (long long cand = 0; cand <= count0; cand++) {
            long long cur = finishTurnForC0(cand);
            if (cur < bestFinish) {
                bestFinish = cur;
                c0 = cand;
            }
        }
    }

    auto owner = [&](long long pos) -> int {
        int a = (int)(pos % K);
        if (a != 0) return a;
        long long t = pos / 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 pos = 0; pos < E; pos++) {
            if (owner(pos) != ID) continue;
            any = true;
            int e = scanEdge[(size_t)pos];
            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 pos = 0; pos < E; pos++) edgesOf[owner(pos)].push_back(scanEdge[(size_t)pos]);

    vector<char> present((size_t)E, 0);
    vector<char> known((size_t)E, 0);
    long long knownCount = 0;

    struct Dsu {
        vector<int> p, r;
        Dsu(int n = 0) : p(n), r(n, 0) { iota(p.begin(), p.end(), 0); }
        int find(int x) {
            while (p[x] != x) { p[x] = p[p[x]]; x = p[x]; }
            return x;
        }
        void unite(int a, int b) {
            a = find(a); b = find(b);
            if (a == b) return;
            if (r[a] < r[b]) swap(a, b);
            p[b] = a;
            if (r[a] == r[b]) r[a]++;
        }
    } dsu(totalRooms);

    vector<vector<pair<int, char>>> openAdj(totalRooms);
    bool solved = false;

    auto addOpenEdge = [&](long long e) {
        int u, v; char uv, vu;
        if (e < H) {
            long long i = e / (m - 1), j = e % (m - 1);
            u = (int)(i * m + j);
            v = u + 1;
            uv = 'R'; vu = 'L';
        } else {
            long long x = e - H, i = x / m, j = x % m;
            u = (int)(i * m + j);
            v = u + m;
            uv = 'D'; vu = 'U';
        }
        openAdj[u].push_back({v, uv});
        openAdj[v].push_back({u, vu});
        dsu.unite(u, v);
        if (dsu.find(startId) == dsu.find(goalId)) solved = true;
    };

    auto setEdge = [&](long long e, int bit) {
        if (!known[e]) {
            known[e] = 1;
            present[e] = (char)bit;
            knownCount++;
            if (bit) addOpenEdge(e);
        }
    };

    auto buildMoves = [&]() {
        vector<int> par(totalRooms, -1);
        vector<char> pdir(totalRooms, 0);
        vector<int> q; q.reserve(totalRooms);
        par[startId] = startId;
        q.push_back(startId);
        for (size_t head = 0; head < q.size(); head++) {
            int cur = q[head];
            if (cur == goalId) break;
            for (auto [nx, d] : openAdj[cur]) {
                if (par[nx] != -1) continue;
                par[nx] = cur;
                pdir[nx] = d;
                q.push_back(nx);
            }
        }
        string dirs;
        for (int cur = goalId; cur != startId; cur = par[cur]) {
            if (cur < 0 || 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); }
        return moves;
    };

    auto claim = [&]() {
        string moves = buildMoves();
        printf("! %s\n", moves.c_str());
        fflush(stdout);
    };

    vector<long long> got(K, 0);
    long long maxMsgs = 0;
    vector<long long> readyTurns;
    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);
        for (long long msg = 1; msg <= msgs; msg++) {
            long long queried = min(msg * bitsPerMsg, bits);
            readyTurns.push_back(queried + msg + 1);
        }
    }
    sort(readyTurns.begin(), readyTurns.end());
    const long long STUCK = 2 * maxMsgs + 5LL * K + 1000;

    auto readMessage = [&]() -> int {
        printf("< ?\n"); fflush(stdout);
        if (!readLine()) return -1;
        if (linebuf[0] == '-') return 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) return 1;
        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]++;
            }
        }
        return 1;
    };

    // Probe our own shard, but read worker chunks at the deterministic turns
    // when they should already be queued. This can expose the final path before
    // agent 0 finishes its local residue shard, while preserving the full-scan
    // fallback and avoiding blind receive misses.
    long long turnNo = 0;
    size_t nextReady = 0;
    size_t ownIdx = 0;
    while (ownIdx < edgesOf[0].size()) {
        if (nextReady < readyTurns.size() && turnNo + 1 >= readyTurns[nextReady]) {
            turnNo++;
            int gotMsg = readMessage();
            nextReady++;
            if (gotMsg < 0) return 0;
            if (solved) { claim(); return 0; }
            continue;
        }

        int e = edgesOf[0][ownIdx++];
        int r, c; edgeCell(e, r, c);
        printf("? %d %d\n", r, c); fflush(stdout);
        turnNo++;
        if (!readLine()) return 0;
        setEdge(e, (linebuf[0] == '1') ? 1 : 0);
        if (solved) { claim(); return 0; }
    }

    long long consecutiveMiss = 0;
    while (knownCount < E && consecutiveMiss < STUCK) {
        int gotMsg = readMessage();
        if (gotMsg < 0) return 0;
        if (gotMsg == 0) consecutiveMiss++;
        else consecutiveMiss = 0;
        if (solved) { claim(); return 0; }
    }

    // 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);
        if (solved) { claim(); return 0; }
    }

    claim();
    return 0;
}
