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

/*
  Midnight Code Cup - Datacenter Imprisonment agent

  Strategy used by default:
    - The official generator is the usual odd-cell perfect maze: all cells with
      odd row and odd column are rooms; possible corridors are exactly the cells
      with one odd and one even coordinate; even/even cells are solid blocks.
    - All scanner agents query disjoint corridor cells.
    - Results are bit-packed into printable ASCII messages and sent to agent 0.
    - Agent 0 reconstructs the room/corridor tree and claims the unique path.

  This uses roughly (N^2/2) / NUM_AGENTS turns for the discovery phase, plus a
  small number of message turns. For N=501 and 50 agents this is about 2.5k turns.

  The protocol uses only Merlin's sanctioned stdin/stdout commands.
*/

struct Cell {
    int r, c;
};

static int N, NUM_AGENTS, AGENT_ID, MAX_MSG_LEN;

[[noreturn]] static void leave_now() {
    exit(0);
}

static string command_reply(const string &cmd) {
    cout << cmd << '\n' << flush;
    string line;
    if (!getline(cin, line)) leave_now();
    return line;
}

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

static void send_msg(int to, const string &body) {
    // body must already be printable ASCII and <= MAX_MSG_LEN bytes.
    string rep = command_reply("> " + to_string(to) + " " + body);
    (void)rep;
}

static pair<int, string> recv_any() {
    string rep = command_reply("< ?");
    if (rep == "- -") return {-1, string()};
    size_t sp = rep.find(' ');
    if (sp == string::npos) return {-1, string()};
    int sender = -1;
    try {
        sender = stoi(rep.substr(0, sp));
    } catch (...) {
        return {-1, string()};
    }
    return {sender, rep.substr(sp + 1)};
}

[[noreturn]] static void halt_agent() {
    cout << "halt\n" << flush;
    leave_now();
}

[[noreturn]] static void claim_path(const string &path) {
    if (path.empty()) {
        cout << "!\n" << flush;
    } else {
        cout << "! " << path << '\n' << flush;
    }
    leave_now();
}

static vector<Cell> build_corridor_candidates() {
    vector<Cell> cells;
    cells.reserve((long long)N * N / 2 + 5);
    for (int r = 1; r <= N; ++r) {
        for (int c = 1; c <= N; ++c) {
            // In the standard odd-cell maze, these are exactly potential corridors.
            if ( (r & 1) ^ (c & 1) ) cells.push_back({r, c});
        }
    }
    return cells;
}

static size_t assigned_count(size_t total, int who, int agents) {
    if ((size_t)who >= total) return 0;
    return (total - 1 - (size_t)who) / (size_t)agents + 1;
}

static string query_my_share(const vector<Cell> &cand, int local_id, int agents) {
    string packed;
    int val = 0, bit = 0;
    for (size_t i = (size_t)local_id; i < cand.size(); i += (size_t)agents) {
        bool open = query_cell(cand[i].r, cand[i].c);
        if (open) val |= (1 << bit);
        ++bit;
        if (bit == 6) {
            packed.push_back(char(33 + val)); // 33..96, all printable and no spaces.
            val = 0;
            bit = 0;
        }
    }
    if (bit) packed.push_back(char(33 + val));
    return packed;
}

static void apply_packed_bits(
    const vector<Cell> &cand,
    int sender,
    const string &body,
    vector<int> &seen_bits,
    vector<unsigned char> &open
) {
    const int stride = NUM_AGENTS;
    for (unsigned char ch : body) {
        int val = int(ch) - 33;
        if (val < 0 || val >= 64) continue; // Should never happen with our encoding.
        for (int b = 0; b < 6; ++b) {
            size_t global_index = (size_t)sender + (size_t)seen_bits[sender] * (size_t)stride;
            if (global_index < cand.size()) {
                const Cell &x = cand[global_index];
                open[(x.r - 1) * N + (x.c - 1)] = (unsigned char)((val >> b) & 1);
            }
            ++seen_bits[sender];
        }
    }
}

static string reconstruct_path(const vector<unsigned char> &open) {
    if (N == 1) return string();

    const int total = N * N;
    const int start = 0;
    const int goal = total - 1;
    if (!open[start] || !open[goal]) return string();

    vector<int> parent(total, -2);
    vector<unsigned char> how(total, 0);
    queue<int> q;
    parent[start] = -1;
    q.push(start);

    const int dr[4] = {-1, 0, 1, 0};
    const int dc[4] = {0, -1, 0, 1};
    const char mv[4] = {'U', 'L', 'D', 'R'};

    while (!q.empty() && parent[goal] == -2) {
        int v = q.front(); q.pop();
        int r = v / N;
        int c = v % N;
        for (int k = 0; k < 4; ++k) {
            int nr = r + dr[k], nc = c + dc[k];
            if (nr < 0 || nr >= N || nc < 0 || nc >= N) continue;
            int u = nr * N + nc;
            if (!open[u] || parent[u] != -2) continue;
            parent[u] = v;
            how[u] = (unsigned char)mv[k];
            q.push(u);
            if (u == goal) break;
        }
    }

    if (parent[goal] == -2) return string();

    string path;
    for (int v = goal; v != start; v = parent[v]) {
        path.push_back((char)how[v]);
    }
    reverse(path.begin(), path.end());
    return path;
}

// Emergency fallback for non-standard mazes. It is intentionally only run by
// agent 0 after the fast odd-cell reconstruction fails. This preserves the fast
// path for the official generator while avoiding a silent hang on unusual tests.
static string slow_single_agent_full_map_fallback(vector<unsigned char> open_known) {
    for (int r = 1; r <= N; ++r) {
        for (int c = 1; c <= N; ++c) {
            int idx = (r - 1) * N + (c - 1);
            // Corridor cells are already known; odd/odd were assumed in fast mode;
            // even/even were assumed walls. Query everything else for safety.
            if (((r & 1) ^ (c & 1))) continue;
            open_known[idx] = (unsigned char)query_cell(r, c);
        }
    }
    return reconstruct_path(open_known);
}

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

    if (!(cin >> N >> NUM_AGENTS >> AGENT_ID >> MAX_MSG_LEN)) return 0;
    string dummy;
    getline(cin, dummy); // consume the end of the startup line

    if (N <= 0 || NUM_AGENTS <= 0 || AGENT_ID < 0 || AGENT_ID >= NUM_AGENTS) return 0;
    if (N == 1) {
        if (AGENT_ID == 0) claim_path("");
        halt_agent();
    }

    const vector<Cell> cand = build_corridor_candidates();
    const int chunk = max(1, min(MAX_MSG_LEN, 256));

    string my_packed = query_my_share(cand, AGENT_ID, NUM_AGENTS);

    if (AGENT_ID != 0) {
        for (size_t pos = 0; pos < my_packed.size(); pos += (size_t)chunk) {
            send_msg(0, my_packed.substr(pos, (size_t)chunk));
        }
        halt_agent();
    }

    // Agent 0 reconstructs the maze.
    vector<unsigned char> open((size_t)N * (size_t)N, 0);

    // Odd/odd room cells are always empty in the official odd-cell perfect maze.
    for (int r = 1; r <= N; r += 2) {
        for (int c = 1; c <= N; c += 2) {
            open[(r - 1) * N + (c - 1)] = 1;
        }
    }

    vector<int> seen_bits(NUM_AGENTS, 0);
    apply_packed_bits(cand, 0, my_packed, seen_bits, open);

    vector<int> expected_chars(NUM_AGENTS, 0), got_chars(NUM_AGENTS, 0);
    for (int id = 0; id < NUM_AGENTS; ++id) {
        size_t bits = assigned_count(cand.size(), id, NUM_AGENTS);
        expected_chars[id] = (int)((bits + 5) / 6);
    }
    got_chars[0] = expected_chars[0];

    int remaining = 0;
    for (int id = 1; id < NUM_AGENTS; ++id) remaining += expected_chars[id];

    while (remaining > 0) {
        auto [sender, body] = recv_any();
        if (sender < 0 || sender >= NUM_AGENTS || sender == 0) continue;
        int useful = min<int>((int)body.size(), expected_chars[sender] - got_chars[sender]);
        if (useful <= 0) continue;
        if (useful < (int)body.size()) body.resize((size_t)useful);
        apply_packed_bits(cand, sender, body, seen_bits, open);
        got_chars[sender] += useful;
        remaining -= useful;
    }

    string path = reconstruct_path(open);
    if (!path.empty()) claim_path(path);

    // This should not be needed on the official generator, but makes the agent
    // more forgiving for local experiments with arbitrary tree mazes.
    string fallback = slow_single_agent_full_map_fallback(open);
    if (!fallback.empty()) claim_path(fallback);

    // If something is badly wrong, leave rather than risking an invalid claim.
    halt_agent();
}
