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

struct Edge {
    int u, v;
    int qr, qc;      // cell to query in the original 1-indexed N x N maze
    char dir;        // move from u to v in the room graph: D or R
};

static const string ALPH = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_";

string ask(const string &s) {
    cout << s << '\n' << flush;
    string r;
    if (!getline(cin, r)) exit(0);
    return r;
}

bool query_cell(int r, int c) {
    string rep = ask("? " + to_string(r) + " " + to_string(c));
    return !rep.empty() && rep[0] == '1';
}

void halt_now() {
    cout << "halt\n" << flush;
    exit(0);
}

void claim_path(const string &path) {
    if (path.empty()) cout << "!\n" << flush;
    else cout << "! " << path << '\n' << flush;
    exit(0);
}

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

vector<Edge> build_edges(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;
            if (i + 1 < m) {
                int v = (i + 1) * m + j;
                // Between grid rooms (2*i+1,2*j+1) and (2*i+3,2*j+1).
                edges.push_back({u, v, 2 * i + 2, 2 * j + 1, 'D'});
            }
            if (j + 1 < m) {
                int v = i * m + (j + 1);
                // Between grid rooms (2*i+1,2*j+1) and (2*i+1,2*j+3).
                edges.push_back({u, v, 2 * i + 1, 2 * j + 2, 'R'});
            }
        }
    }
    return edges;
}

long long count_owned_edges(int total_edges, int owner, int agents) {
    if (owner < 0 || owner >= agents || owner >= total_edges) return 0;
    return (total_edges - 1 - owner) / agents + 1;
}

string encode_bits(const vector<unsigned char> &bits) {
    string out;
    out.reserve((bits.size() + 5) / 6);
    for (size_t i = 0; i < bits.size(); i += 6) {
        int v = 0;
        for (int b = 0; b < 6 && i + b < bits.size(); ++b) {
            if (bits[i + b]) v |= (1 << b);
        }
        out.push_back(ALPH[v]);
    }
    return out;
}

int decode_value(char ch) {
    if ('0' <= ch && ch <= '9') return ch - '0';
    if ('A' <= ch && ch <= 'Z') return 10 + (ch - 'A');
    if ('a' <= ch && ch <= 'z') return 36 + (ch - 'a');
    if (ch == '-') return 62;
    if (ch == '_') return 63;
    return 0; // Should never happen with our own agents.
}

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

    int N, NUM_AGENTS, AGENT_ID, MAX_MSG_LEN;
    if (!(cin >> N >> NUM_AGENTS >> AGENT_ID >> MAX_MSG_LEN)) return 0;
    string dummy;
    getline(cin, dummy);

    if (N == 1) {
        if (AGENT_ID == 0) claim_path("");
        halt_now();
    }

    const int m = (N + 1) / 2;          // number of always-empty rooms per side
    vector<Edge> edges = build_edges(m);
    const int E = (int)edges.size();
    const int V = m * m;

    // If messages were somehow disabled, fall back to a safe single-agent solver.
    const int effective_agents = (MAX_MSG_LEN >= 1 ? NUM_AGENTS : 1);
    if (AGENT_ID >= effective_agents) halt_now();

    // Query this agent's share of candidate corridor cells.
    vector<unsigned char> my_bits;
    my_bits.reserve((E + effective_agents - 1) / effective_agents + 1);

    // Agent 0 will store the whole discovered room-tree.
    vector<signed char> open;
    if (AGENT_ID == 0) open.assign(E, -1);

    for (int eid = AGENT_ID; eid < E; eid += effective_agents) {
        bool ok = query_cell(edges[eid].qr, edges[eid].qc);
        if (AGENT_ID == 0) open[eid] = ok ? 1 : 0;
        else my_bits.push_back(ok ? 1 : 0);
    }

    if (AGENT_ID != 0) {
        string enc = encode_bits(my_bits);
        const int chunk = max(1, MAX_MSG_LEN);
        for (int pos = 0; pos < (int)enc.size(); pos += chunk) {
            string body = enc.substr(pos, chunk);
            string rep = ask("> 0 " + body);
            (void)rep;
        }
        halt_now();
    }

    // Agent 0 receives all compressed bit streams from the other agents.
    vector<int> expected_chunks(effective_agents, 0), got_chunks(effective_agents, 0);
    vector<string> received(effective_agents);
    int need = 0, got_total = 0;
    const int chunk = max(1, MAX_MSG_LEN);
    for (int s = 1; s < effective_agents; ++s) {
        long long bits = count_owned_edges(E, s, effective_agents);
        long long chars = (bits + 5) / 6;
        expected_chunks[s] = (int)((chars + chunk - 1) / chunk);
        need += expected_chunks[s];
        received[s].reserve((size_t)chars);
    }

    while (got_total < need) {
        string rep = ask("< ?");
        if (rep == "- -") continue;
        size_t sp = rep.find(' ');
        if (sp == string::npos) continue;
        int sender = -1;
        try {
            sender = stoi(rep.substr(0, sp));
        } catch (...) {
            continue;
        }
        string body = rep.substr(sp + 1);
        if (1 <= sender && sender < effective_agents && got_chunks[sender] < expected_chunks[sender]) {
            received[sender] += body;
            ++got_chunks[sender];
            ++got_total;
        }
    }

    for (int s = 1; s < effective_agents; ++s) {
        long long bit_count = count_owned_edges(E, s, effective_agents);
        long long bit_idx = 0;
        for (char ch : received[s]) {
            int val = decode_value(ch);
            for (int b = 0; b < 6 && bit_idx < bit_count; ++b, ++bit_idx) {
                int eid = s + (int)bit_idx * effective_agents;
                if (eid < E) open[eid] = ((val >> b) & 1) ? 1 : 0;
            }
        }
    }

    // Construct the known room tree and find the unique room path from corner to corner.
    vector<vector<pair<int, char>>> adj(V);
    for (int eid = 0; eid < E; ++eid) {
        if (open[eid] != 1) continue;
        const Edge &e = edges[eid];
        adj[e.u].push_back({e.v, e.dir});
        adj[e.v].push_back({e.u, opposite_dir(e.dir)});
    }

    int start = 0, goal = V - 1;
    vector<int> parent(V, -2);
    vector<char> pdir(V, 0);
    queue<int> q;
    parent[start] = -1;
    q.push(start);
    while (!q.empty() && parent[goal] == -2) {
        int u = q.front();
        q.pop();
        for (auto [v, d] : adj[u]) {
            if (parent[v] != -2) continue;
            parent[v] = u;
            pdir[v] = d;
            q.push(v);
            if (v == goal) break;
        }
    }

    // The generator guarantees connectivity; this fallback should never be used.
    if (parent[goal] == -2) {
        // Avoid an invalid claim if something unexpected happened.
        halt_now();
    }

    string room_path;
    for (int cur = goal; cur != start; cur = parent[cur]) {
        room_path.push_back(pdir[cur]);
    }
    reverse(room_path.begin(), room_path.end());

    string answer;
    answer.reserve(room_path.size() * 2);
    for (char d : room_path) {
        answer.push_back(d);
        answer.push_back(d); // one step into the corridor, one step into the next room
    }

    claim_path(answer);
    return 0;
}
