// Problem D: Datacenter Imprisonment
//
// Hybrid exact mapper.
//
// For low/medium agent counts this uses the proven synchronized progressive
// bitmap mapper: all agents query a geometry-prioritized global edge order in
// blocks, then non-leaders send one bitmap to agent 0.
//
// For high agent counts, agent 0 becomes a pure receiver and all other agents
// keep querying while their previous block messages are drained.  This loses one
// query worker but removes the O(NUM_AGENTS) idle collection gap.  Correctness
// does not depend on either heuristic: every room-wall edge is eventually
// queried, and agent 0 claims only after the known-open graph connects the two
// corners.

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

struct Edge {
    int u;
    int v;
    int wr;
    int wc;
    char dir;
};

struct DSU {
    vector<int> p, sz;
    explicit DSU(int n = 0) { 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;
    }
    void unite(int a, int b) {
        a = find(a);
        b = find(b);
        if (a == b) return;
        if (sz[a] < sz[b]) swap(a, b);
        p[b] = a;
        sz[a] += sz[b];
    }
    bool same(int a, int b) { return find(a) == find(b); }
};

static int N, NUM_AGENTS, AGENT_ID, MAX_MSG_LEN;
static constexpr int SYNC_BLOCK = 320;
static constexpr int PIPE_BLOCK = 64;
static constexpr int PIPE_THRESHOLD = 20;
static constexpr int BLOCK_CHARS = 3;
static const string ALPH =
    "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_~";

static string readLine() {
    string s;
    if (!getline(cin, s)) return string();
    return s;
}

static bool queryEmpty(int r, int c) {
    cout << "? " << r << ' ' << c << '\n' << flush;
    string reply = readLine();
    return !reply.empty() && reply[0] == '1';
}

static void sendMsg(int dst, const string &body) {
    cout << "> " << dst << ' ' << body << '\n' << flush;
    (void)readLine();
}

static string recvAny() {
    cout << "< ?\n" << flush;
    return readLine();
}

static void skipTurn() {
    cout << ".\n" << flush;
    (void)readLine();
}

static void haltNow() {
    cout << "halt\n" << flush;
}

static int alphaVal(char c) {
    size_t p = ALPH.find(c);
    return p == string::npos ? -1 : (int)p;
}

static void appendCode(string &s, int x, int chars) {
    for (int sh = 6 * (chars - 1); sh >= 0; sh -= 6) {
        s.push_back(ALPH[(x >> sh) & 63]);
    }
}

static int readCode(const string &s, int pos, int chars) {
    int x = 0;
    for (int i = 0; i < chars; ++i) {
        int v = (pos + i < (int)s.size()) ? alphaVal(s[pos + i]) : -1;
        if (v < 0) return -1;
        x = (x << 6) | v;
    }
    return x;
}

static char oppositeDir(char c) {
    if (c == 'D') return 'U';
    if (c == 'U') return 'D';
    if (c == 'R') return 'L';
    return 'R';
}

static vector<Edge> buildEdges(int m) {
    vector<Edge> edges;
    edges.reserve(2 * m * max(0, m - 1));
    for (int i = 0; i < m; ++i) {
        for (int j = 0; j < m; ++j) {
            int u = i * m + j;
            int gr = 2 * i + 1;
            int gc = 2 * j + 1;
            if (i + 1 < m) {
                int v = (i + 1) * m + j;
                edges.push_back({u, v, gr + 1, gc, 'D'});
            }
            if (j + 1 < m) {
                int v = i * m + (j + 1);
                edges.push_back({u, v, gr, gc + 1, 'R'});
            }
        }
    }
    return edges;
}

static vector<int> buildPriorityOrder(const vector<Edge> &edges, int m) {
    int maxScore = 24 * max(1, m) + 32;
    vector<vector<int>> buckets(maxScore);
    for (int id = 0; id < (int)edges.size(); ++id) {
        int u = edges[id].u;
        int v = edges[id].v;
        int i1 = u / m, j1 = u % m;
        int i2 = v / m, j2 = v % m;
        int x2 = i1 + i2;
        int y2 = j1 + j2;
        int diag2 = abs(x2 - y2);
        int anti2 = abs(x2 + y2 - 2 * (m - 1));
        int score = 10 * diag2 + anti2;
        if (score >= (int)buckets.size()) buckets.resize(score + 1);
        buckets[score].push_back(id);
    }
    vector<int> order;
    order.reserve(edges.size());
    for (auto &bucket : buckets) {
        for (int id : bucket) order.push_back(id);
    }
    return order;
}

static void addOpenEdge(vector<array<int, 4>> &nbr,
                        vector<array<char, 4>> &ndir,
                        vector<unsigned char> &deg,
                        vector<unsigned char> &known,
                        DSU &dsu,
                        int eid,
                        const vector<Edge> &edges) {
    if (eid < 0 || eid >= (int)edges.size() || known[eid]) return;
    known[eid] = 1;
    const Edge &e = edges[eid];
    int du = deg[e.u]++;
    nbr[e.u][du] = e.v;
    ndir[e.u][du] = e.dir;
    int dv = deg[e.v]++;
    nbr[e.v][dv] = e.u;
    ndir[e.v][dv] = oppositeDir(e.dir);
    dsu.unite(e.u, e.v);
}

static string buildClaimPath(const vector<array<int, 4>> &nbr,
                             const vector<array<char, 4>> &ndir,
                             const vector<unsigned char> &deg,
                             int V) {
    vector<int> parent(V, -1);
    vector<char> pdir(V, 0);
    queue<int> q;
    q.push(0);
    parent[0] = 0;
    while (!q.empty() && parent[V - 1] == -1) {
        int u = q.front();
        q.pop();
        for (int k = 0; k < deg[u]; ++k) {
            int v = nbr[u][k];
            if (parent[v] != -1) continue;
            parent[v] = u;
            pdir[v] = ndir[u][k];
            q.push(v);
            if (v == V - 1) break;
        }
    }

    vector<char> dirs;
    for (int cur = V - 1; cur != 0 && cur != -1; cur = parent[cur]) {
        dirs.push_back(pdir[cur]);
    }
    reverse(dirs.begin(), dirs.end());

    string path;
    path.reserve(2 * dirs.size());
    for (char c : dirs) {
        path.push_back(c);
        path.push_back(c);
    }
    return path;
}

static void claimNow(const vector<array<int, 4>> &nbr,
                     const vector<array<char, 4>> &ndir,
                     const vector<unsigned char> &deg,
                     int V) {
    cout << "! " << buildClaimPath(nbr, ndir, deg, V) << '\n' << flush;
}

static string encodeBitmap(char tag, int blockId,
                           const vector<unsigned char> &bits,
                           int blockSize) {
    string body;
    body.reserve(1 + (tag == 'P' ? BLOCK_CHARS : 0) + (blockSize + 5) / 6);
    body.push_back(tag);
    if (tag == 'P') appendCode(body, blockId, BLOCK_CHARS);
    for (int c = 0; c < (blockSize + 5) / 6; ++c) {
        int val = 0;
        for (int b = 0; b < 6; ++b) {
            int t = c * 6 + b;
            if (t < blockSize && bits[t]) val |= (1 << b);
        }
        body.push_back(ALPH[val]);
    }
    return body;
}

static bool parseSenderBody(const string &line, int &sender, string &body) {
    if (line == "- -" || line.empty()) return false;
    size_t sp = line.find(' ');
    if (sp == string::npos) return false;
    sender = stoi(line.substr(0, sp));
    body = line.substr(sp + 1);
    return true;
}

static void runSynchronized(int V, const vector<Edge> &edges,
                            const vector<int> &order) {
    int E = (int)order.size();
    int blockSize = min(SYNC_BLOCK, max(1, (MAX_MSG_LEN - 1) * 6));
    int phases = (E + blockSize * NUM_AGENTS - 1) / (blockSize * NUM_AGENTS);

    vector<array<int, 4>> nbr;
    vector<array<char, 4>> ndir;
    vector<unsigned char> deg, known;
    DSU dsu;
    if (AGENT_ID == 0) {
        nbr.resize(V);
        ndir.resize(V);
        deg.assign(V, 0);
        known.assign(E, 0);
        dsu.init(V);
        if (V == 1) {
            claimNow(nbr, ndir, deg, V);
            return;
        }
    }

    for (int phase = 0; phase < phases; ++phase) {
        vector<unsigned char> bits(blockSize, 0);
        int phaseBase = phase * blockSize * NUM_AGENTS;
        for (int t = 0; t < blockSize; ++t) {
            int pos = phaseBase + t * NUM_AGENTS + AGENT_ID;
            if (pos >= E) break;
            int eid = order[pos];
            const Edge &e = edges[eid];
            if (queryEmpty(e.wr, e.wc)) {
                bits[t] = 1;
                if (AGENT_ID == 0) {
                    addOpenEdge(nbr, ndir, deg, known, dsu, eid, edges);
                }
            }
        }

        if (AGENT_ID == 0) {
            if (dsu.same(0, V - 1)) {
                claimNow(nbr, ndir, deg, V);
                return;
            }
            if (NUM_AGENTS > 1) skipTurn();
            for (int got = 0; got < NUM_AGENTS - 1; ) {
                string line = recvAny();
                int sender = -1;
                string body;
                if (!parseSenderBody(line, sender, body)) continue;
                if (sender <= 0 || sender >= NUM_AGENTS ||
                    body.empty() || body[0] != 'B') {
                    continue;
                }
                ++got;
                for (int c = 0; c + 1 < (int)body.size(); ++c) {
                    int val = alphaVal(body[c + 1]);
                    if (val < 0) continue;
                    for (int b = 0; b < 6; ++b) {
                        if ((val & (1 << b)) == 0) continue;
                        int t = c * 6 + b;
                        if (t >= blockSize) continue;
                        int pos = phaseBase + t * NUM_AGENTS + sender;
                        if (pos >= E) continue;
                        addOpenEdge(nbr, ndir, deg, known, dsu,
                                    order[pos], edges);
                    }
                }
                if (dsu.same(0, V - 1)) {
                    claimNow(nbr, ndir, deg, V);
                    return;
                }
            }
        } else {
            sendMsg(0, encodeBitmap('B', 0, bits, blockSize));
            for (int i = 0; i < NUM_AGENTS - 1; ++i) skipTurn();
        }
    }

    if (AGENT_ID == 0) {
        claimNow(nbr, ndir, deg, V);
    } else {
        haltNow();
    }
}

static void runPipelined(int V, const vector<Edge> &edges,
                         const vector<int> &order) {
    int E = (int)order.size();
    int workers = NUM_AGENTS - 1;
    int blockSize = min(PIPE_BLOCK, max(1, (MAX_MSG_LEN - 1 - BLOCK_CHARS) * 6));
    if (blockSize < workers) {
        runSynchronized(V, edges, order);
        return;
    }

    if (AGENT_ID != 0) {
        int worker = AGENT_ID - 1;
        for (int block = 0; ; ++block) {
            vector<unsigned char> bits(blockSize, 0);
            bool any = false;
            for (int t = 0; t < blockSize; ++t) {
                long long seq = 1LL * block * blockSize + t;
                long long pos = seq * workers + worker;
                if (pos >= E) break;
                any = true;
                int eid = order[(int)pos];
                const Edge &e = edges[eid];
                if (queryEmpty(e.wr, e.wc)) bits[t] = 1;
            }
            if (!any) {
                sendMsg(0, "X");
                haltNow();
                return;
            }
            sendMsg(0, encodeBitmap('P', block, bits, blockSize));
        }
    }

    vector<array<int, 4>> nbr(V);
    vector<array<char, 4>> ndir(V);
    vector<unsigned char> deg(V, 0), known(E, 0);
    DSU dsu(V);
    if (V == 1) {
        claimNow(nbr, ndir, deg, V);
        return;
    }

    int done = 0;
    while (true) {
        string line = recvAny();
        int sender = -1;
        string body;
        if (!parseSenderBody(line, sender, body)) continue;
        if (sender <= 0 || sender >= NUM_AGENTS || body.empty()) continue;
        if (body[0] == 'X') {
            ++done;
            if (done >= workers && dsu.same(0, V - 1)) {
                claimNow(nbr, ndir, deg, V);
                return;
            }
            continue;
        }
        if (body[0] != 'P') continue;
        int block = readCode(body, 1, BLOCK_CHARS);
        if (block < 0) continue;
        int worker = sender - 1;
        for (int c = 0; c + 1 + BLOCK_CHARS < (int)body.size(); ++c) {
            int val = alphaVal(body[1 + BLOCK_CHARS + c]);
            if (val < 0) continue;
            for (int b = 0; b < 6; ++b) {
                if ((val & (1 << b)) == 0) continue;
                int t = c * 6 + b;
                if (t >= blockSize) continue;
                long long seq = 1LL * block * blockSize + t;
                long long pos = seq * workers + worker;
                if (pos >= E) continue;
                addOpenEdge(nbr, ndir, deg, known, dsu,
                            order[(int)pos], edges);
            }
        }
        if (dsu.same(0, V - 1)) {
            claimNow(nbr, ndir, deg, V);
            return;
        }
    }
}

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

    {
        string line = readLine();
        istringstream in(line);
        in >> N >> NUM_AGENTS >> AGENT_ID >> MAX_MSG_LEN;
    }

    int m = (N + 1) / 2;
    int V = m * m;
    vector<Edge> edges = buildEdges(m);
    vector<int> order = buildPriorityOrder(edges, m);

    if (NUM_AGENTS >= PIPE_THRESHOLD) {
        runPipelined(V, edges, order);
    } else {
        runSynchronized(V, edges, order);
    }
    return 0;
}
