// D. Datacenter Imprisonment — cooperative maze-mapping agents.
//
// Maze structure (from merlin-source/src/maze.rs): N is odd, rooms sit at
// odd (r,c) and are always empty; cells with both coordinates even are always
// walls; the only unknown cells are the 2*m*(m-1) "edge" cells between
// adjacent rooms (m = (N+1)/2). The empty cells form a spanning tree of the
// rooms, so learning every edge bit fully determines the unique path.
//
// Plan: split the edge list across agents. Workers query their share, pack
// the resulting bits 6-per-character into printable ASCII, and ship them to
// agent 0. Agent 0 queries its own (smaller) share, collects everything,
// rebuilds the grid, BFS-es the path and claims it.
//
// Tree inference: empty cells are acyclic, so an edge whose two rooms are
// already connected through locally-known open edges must be a wall — no
// query needed. Each agent runs a union-find over its own chunk's results
// to skip such queries (the inferred bit is still shipped to the leader).

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

static string ask(const string &cmd) {
    fputs(cmd.c_str(), stdout);
    fputc('\n', stdout);
    fflush(stdout);
    string line;
    if (!getline(cin, line)) exit(0);
    return line;
}

int main() {
    long long N, K, ID, L;
    if (!(cin >> N >> K >> ID >> L)) return 0;
    cin.ignore(numeric_limits<streamsize>::max(), '\n');

    if (N == 1) {
        if (ID == 0) {
            fputs("!\n", stdout);
            fflush(stdout);
        } else {
            fputs("halt\n", stdout);
            fflush(stdout);
        }
        return 0;
    }

    const long long m = (N + 1) / 2;
    // Deterministic edge order shared by all agents.
    vector<pair<int, int>> edges;
    edges.reserve(2 * m * (m - 1));
    for (long long i = 0; i < m; i++)
        for (long long j = 0; j < m; j++) {
            if (i + 1 < m) edges.push_back({(int)(2 * i + 2), (int)(2 * j + 1)});
            if (j + 1 < m) edges.push_back({(int)(2 * i + 1), (int)(2 * j + 2)});
        }
    const long long E = (long long)edges.size();
    const long long bitsPerMsg = 6 * L;

    // Partition: leader (agent 0) takes a reduced share to offset the turns it
    // spends receiving worker messages.
    const long long W = K - 1;
    long long El = E; // leader share
    vector<long long> cnt(K, 0);
    if (W > 0) {
        long long M = (E + bitsPerMsg - 1) / bitsPerMsg;
        long long Ew = 0;
        for (int iter = 0; iter < 4; iter++) {
            Ew = (E + M + K - 1) / K;
            El = E - W * Ew;
            if (El < 0) El = 0;
            long long rest = E - El, base = rest / W, rem = rest % W;
            M = 0;
            for (long long w = 0; w < W; w++) {
                long long c = base + (w < rem ? 1 : 0);
                M += (c + bitsPerMsg - 1) / bitsPerMsg;
            }
        }
        cnt[0] = El;
        long long rest = E - El, base = rest / W, rem = rest % W;
        for (long long w = 0; w < W; w++) cnt[w + 1] = base + (w < rem ? 1 : 0);
    } else {
        cnt[0] = E;
    }
    vector<long long> start(K + 1, 0);
    for (long long a = 0; a < K; a++) start[a + 1] = start[a] + cnt[a];

    // Union-find over rooms; room (i,j) -> i*m+j. Edge cell (r,c): if r is
    // even it joins rooms vertically, else horizontally.
    vector<int> dsu(m * m);
    iota(dsu.begin(), dsu.end(), 0);
    function<int(int)> find = [&](int x) {
        while (dsu[x] != x) x = dsu[x] = dsu[dsu[x]];
        return x;
    };
    auto edgeRooms = [&](long long i) -> pair<int, int> {
        int r = edges[i].first, c = edges[i].second;
        if (r % 2 == 0) {
            int a = (r / 2 - 1) * (int)m + (c - 1) / 2;
            return {a, a + (int)m};
        }
        int a = ((r - 1) / 2) * (int)m + (c / 2 - 1);
        return {a, a + 1};
    };
    // Returns the bit for edge i, querying only when not inferable.
    auto probe = [&](long long i) -> int {
        auto [u, v] = edgeRooms(i);
        int ru = find(u), rv = find(v);
        if (ru == rv) return 0; // would form a cycle -> must be wall
        string r = ask("? " + to_string(edges[i].first) + " " + to_string(edges[i].second));
        if (r == "1") {
            dsu[ru] = rv;
            return 1;
        }
        return 0;
    };

    if (ID != 0) {
        long long lo = start[ID], hi = start[ID + 1];
        if (lo >= hi) {
            fputs("halt\n", stdout);
            fflush(stdout);
            return 0;
        }
        string buf;
        int acc = 0, accn = 0;
        auto flushMsg = [&]() {
            if (buf.empty()) return;
            ask("> 0 " + buf); // reply: OK
            buf.clear();
        };
        for (long long i = lo; i < hi; i++) {
            int b = probe(i);
            acc = (acc << 1) | b;
            if (++accn == 6) {
                buf.push_back((char)('0' + acc));
                acc = accn = 0;
                if ((long long)buf.size() == L) flushMsg();
            }
        }
        if (accn > 0) buf.push_back((char)('0' + (acc << (6 - accn))));
        flushMsg();
        fputs("halt\n", stdout);
        fflush(stdout);
        return 0;
    }

    // Leader.
    vector<char> bit(E, 0);
    for (long long i = 0; i < El; i++) bit[i] = (char)probe(i);
    // Expected chars from each worker; decode in-order per sender.
    vector<long long> expChars(K, 0), gotChars(K, 0), nextBit(K, 0);
    long long remaining = 0;
    for (long long a = 1; a < K; a++) {
        expChars[a] = (cnt[a] + 5) / 6;
        nextBit[a] = start[a];
        remaining += expChars[a];
    }
    while (remaining > 0) {
        string r = ask("< ?");
        if (r == "- -") continue;
        size_t sp = r.find(' ');
        long long s = stoll(r.substr(0, sp));
        string body = r.substr(sp + 1);
        for (char ch : body) {
            int v = ch - '0';
            for (int k = 5; k >= 0; k--) {
                if (nextBit[s] < start[s + 1]) bit[nextBit[s]++] = (v >> k) & 1;
            }
        }
        gotChars[s] += (long long)body.size();
        remaining -= (long long)body.size();
    }

    // Rebuild grid and BFS (rooms are always empty).
    long long S = N + 2;
    vector<char> open(S * S, 0);
    for (long long i = 0; i < m; i++)
        for (long long j = 0; j < m; j++) open[(2 * i + 1) * S + (2 * j + 1)] = 1;
    for (long long i = 0; i < E; i++)
        if (bit[i]) open[(long long)edges[i].first * S + edges[i].second] = 1;

    vector<int> par(S * S, -1);
    vector<char> pmove(S * S, 0);
    deque<int> q;
    int startCell = (int)(1 * S + 1), goal = (int)(N * S + N);
    par[startCell] = startCell;
    q.push_back(startCell);
    const int dr[4] = {-1, 1, 0, 0}, dc[4] = {0, 0, -1, 1};
    const char mv[4] = {'U', 'D', 'L', 'R'};
    while (!q.empty()) {
        int cur = q.front();
        q.pop_front();
        if (cur == goal) break;
        int r = cur / (int)S, c = cur % (int)S;
        for (int d = 0; d < 4; d++) {
            int nr = r + dr[d], nc = c + dc[d];
            int nx = nr * (int)S + nc;
            if (nr < 1 || nc < 1 || nr > N || nc > N) continue;
            if (!open[nx] || par[nx] != -1) continue;
            par[nx] = cur;
            pmove[nx] = mv[d];
            q.push_back(nx);
        }
    }
    string path;
    for (int cur = goal; cur != startCell; cur = par[cur]) path.push_back(pmove[cur]);
    reverse(path.begin(), path.end());
    fputs(("! " + path + "\n").c_str(), stdout);
    fflush(stdout);
    return 0;
}
