#pragma GCC optimize("O2")
#include <cstdlib>
#include <iostream>
#include <vector>
#include <string>
#include <queue>
#include <algorithm>
#include <cmath>

using namespace std;

int N, NUM_AGENTS, AGENT_ID, MAX_MSG_LEN;

int probe(int r, int c) {
    cout << "? " << r << " " << c << endl;
    string ans;
    if (!(cin >> ans)) exit(0);
    return ans == "1" ? 1 : 0;
}

void send_msg(int target, const string& msg) {
    cout << "> " << target << " " << msg << endl;
    string ok;
    if (!(cin >> ok)) exit(0);
}

// returns "" if none, else "sender body"
string recv_any() {
    cout << "< ?" << endl;
    string sender, body;
    if (!(cin >> sender)) exit(0);
    cin >> body;
    if (sender == "-") return "";
    return sender + " " + body;
}

void do_halt() {
    cout << "halt" << endl;
    exit(0);
}

long long g_redundant = 0, g_decoded = 0;
void claim(const string& path) {
    fprintf(stderr, "[dbg] decoded=%lld redundant=%lld\n", g_decoded, g_redundant);
    cout << "! " << path << endl;
    exit(0);
}

char to_b64(int val) {
    if (val < 26) return 'A' + val;
    if (val < 52) return 'a' + (val - 26);
    if (val < 62) return '0' + (val - 52);
    if (val == 62) return '-';
    return '_';
}
int from_b64(char c) {
    if (c >= 'A' && c <= 'Z') return c - 'A';
    if (c >= 'a' && c <= 'z') return c - 'a' + 26;
    if (c >= '0' && c <= '9') return c - '0' + 52;
    if (c == '-') return 62;
    return 63;
}

struct Edge {
    int r, c;   // grid coords of corridor cell
    int u, v;   // room node ids
    int id;
    bool operator<(const Edge& o) const {
        int d1 = abs(r - c), d2 = abs(o.r - o.c);
        if (d1 != d2) return d1 < d2;
        int s1 = r + c, s2 = o.r + o.c;
        if (s1 != s2) return s1 < s2;
        if (r != o.r) return r < o.r;
        return c < o.c;
    }
};

// ---------- globals for agent 0 brain ----------
int m, V, E;
vector<Edge> edges_by_id;          // indexed by id
vector<int> status_e;              // -1 unknown, 0 closed, 1 open
vector<vector<int>> room_edges;    // room -> incident edge ids
int openCnt = 0, closedCnt = 0;
vector<int> dsu_p;
int dsu_find(int x){ while(dsu_p[x]!=x){ dsu_p[x]=dsu_p[dsu_p[x]]; x=dsu_p[x]; } return x; }

void set_status(int id, int st);

void dsu_unite(int a, int b){
    a = dsu_find(a); b = dsu_find(b);
    if (a != b) dsu_p[b] = a;
}

void set_status(int id, int st) {
    if (status_e[id] != -1) return;
    status_e[id] = st;
    if (st == 1) { openCnt++; dsu_unite(edges_by_id[id].u, edges_by_id[id].v); }
    else closedCnt++;
}

// inference fixpoint: cycle rule, cut rule, count rule
void infer() {
    bool changed = true;
    int closedTarget = (m - 1) * (m - 1);
    int openTarget = V - 1;
    static vector<int> bcount, bedge;
    bcount.assign(V, 0); bedge.assign(V, -1);
    while (changed) {
        changed = false;
        // cycle rule
        for (int id = 0; id < E; id++) {
            if (status_e[id] != -1) continue;
            if (dsu_find(edges_by_id[id].u) == dsu_find(edges_by_id[id].v)) {
                set_status(id, 0); changed = true;
            }
        }
        // count rule
        if (closedCnt == closedTarget && openCnt < openTarget) {
            for (int id = 0; id < E; id++) if (status_e[id] == -1) set_status(id, 1);
            changed = true; continue;
        }
        if (openCnt == openTarget && openCnt + closedCnt < E) {
            for (int id = 0; id < E; id++) if (status_e[id] == -1) set_status(id, 0);
            break;
        }
        // cut rule: component with exactly one unknown boundary edge -> open
        fill(bcount.begin(), bcount.end(), 0);
        for (int id = 0; id < E; id++) {
            if (status_e[id] != -1) continue;
            int ru = dsu_find(edges_by_id[id].u), rv = dsu_find(edges_by_id[id].v);
            bcount[ru]++; bedge[ru] = id;
            bcount[rv]++; bedge[rv] = id;
        }
        for (int r = 0; r < V; r++) {
            if (bcount[r] == 1 && openCnt < openTarget) {
                int id = bedge[r];
                if (status_e[id] == -1) { set_status(id, 1); changed = true; }
            }
        }
    }
}

// if start & goal connected via known-open edges, build and claim path
void try_claim() {
    if (dsu_find(0) != dsu_find(V - 1)) return;
    // BFS over open edges
    vector<int> parent(V, -2);
    parent[0] = -1;
    queue<int> q; q.push(0);
    while (!q.empty()) {
        int u = q.front(); q.pop();
        if (u == V - 1) break;
        for (int id : room_edges[u]) {
            if (status_e[id] != 1) continue;
            int w = edges_by_id[id].u == u ? edges_by_id[id].v : edges_by_id[id].u;
            if (parent[w] == -2) { parent[w] = u; q.push(w); }
        }
    }
    if (parent[V - 1] == -2) return; // shouldn't happen
    string path;
    int cur = V - 1;
    while (cur != 0) {
        int p = parent[cur];
        if (cur == p + 1) path += "RR";
        else if (cur == p - 1) path += "LL";
        else if (cur == p + m) path += "DD";
        else path += "UU";
        cur = p;
    }
    reverse(path.begin(), path.end());
    claim(path);
}

// multi-source BFS from all rooms in component `root`, through non-closed edges
void comp_bfs(int root, vector<int>& dist) {
    dist.assign(V, -1);
    queue<int> q;
    for (int r = 0; r < V; r++) if (dsu_find(r) == root) { dist[r] = 0; q.push(r); }
    while (!q.empty()) {
        int u = q.front(); q.pop();
        for (int id : room_edges[u]) {
            if (status_e[id] == 0) continue;
            int w = edges_by_id[id].u == u ? edges_by_id[id].v : edges_by_id[id].u;
            if (dist[w] < 0) { dist[w] = dist[u] + 1; q.push(w); }
        }
    }
}

int main(int argc, char** argv) {
    if (!(cin >> N >> NUM_AGENTS >> AGENT_ID >> MAX_MSG_LEN)) return 0;

    m = (N + 1) / 2;
    V = m * m;
    E = 2 * m * (m - 1);

    if (m == 1) {
        if (AGENT_ID == 0) claim("");
        do_halt();
    }

    vector<Edge> all_edges;
    int id_counter = 0;
    for (int i = 0; i < m; i++)
        for (int j = 0; j < m; j++) {
            int u = i * m + j;
            if (j + 1 < m) all_edges.push_back({2*i+1, 2*j+2, u, u+1, id_counter++});
        }
    for (int i = 0; i < m; i++)
        for (int j = 0; j < m; j++) {
            int u = i * m + j;
            if (i + 1 < m) all_edges.push_back({2*i+2, 2*j+1, u, u+m, id_counter++});
        }

    edges_by_id = all_edges; // id == index
    vector<Edge> sorted_edges = all_edges;
    int order_mode = 3;
    { const char* env = getenv("MERLIN_ORD"); if (env) order_mode = atoi(env); }
    if (order_mode == 0) {
        sort(sorted_edges.begin(), sorted_edges.end());
    } else if (order_mode == 2) {
        // order by |electrical current| through edge (UST path probability), descending
        vector<double> phi(V, 0.0);
        vector<double> b(V, 0.0);
        b[0] = 1.0; b[V - 1] = -1.0;
        // Gauss-Seidel with SOR on grid Laplacian (pin nothing; project mean)
        double omega = 1.9;
        for (int it = 0; it < 3000; it++) {
            for (int i = 0; i < m; i++) for (int j = 0; j < m; j++) {
                int u = i * m + j;
                double sum = 0; int deg = 0;
                if (i > 0)     { sum += phi[u - m]; deg++; }
                if (i + 1 < m) { sum += phi[u + m]; deg++; }
                if (j > 0)     { sum += phi[u - 1]; deg++; }
                if (j + 1 < m) { sum += phi[u + 1]; deg++; }
                double newv = (sum + b[u]) / deg;
                phi[u] += omega * (newv - phi[u]);
            }
        }
        vector<double> cur(E, 0.0);
        for (auto& e : edges_by_id) cur[e.id] = fabs(phi[e.u] - phi[e.v]);
        sort(sorted_edges.begin(), sorted_edges.end(), [&](const Edge& a, const Edge& bb) {
            if (cur[a.id] != cur[bb.id]) return cur[a.id] > cur[bb.id];
            if (a.r != bb.r) return a.r < bb.r;
            return a.c < bb.c;
        });
    } else if (order_mode == 3) {
        // band order, but middle of the maze first within each ring
        sort(sorted_edges.begin(), sorted_edges.end(), [&](const Edge& a, const Edge& b) {
            auto key = [&](const Edge& e) {
                return make_tuple(abs(e.r - e.c), abs(e.r + e.c - (N + 1)), e.r, e.c);
            };
            return key(a) < key(b);
        });
    } else {
        // balls growing from both corners; tie-break toward diagonal
        sort(sorted_edges.begin(), sorted_edges.end(), [&](const Edge& a, const Edge& b) {
            auto key = [&](const Edge& e) {
                int d1 = (e.r - 1) + (e.c - 1);
                int d2 = (N - e.r) + (N - e.c);
                return make_tuple(min(d1, d2), abs(e.r - e.c), e.r, e.c);
            };
            return key(a) < key(b);
        });
    }

    int senders = max(1, NUM_AGENTS - 1);
    vector<vector<Edge>> sender_edges(NUM_AGENTS); // index by agent id (1..A-1)
    if (NUM_AGENTS > 1) {
        for (int k = 0; k < E; k++)
            sender_edges[1 + (k % senders)].push_back(sorted_edges[k]);
    }

    // chunk size in bits; must fit MAX_MSG_LEN chars (6 bits/char)
    int W = E / (senders * 15);
    if (W < 12) W = 12;
    if (W > 40) W = 40;
    { const char* env = getenv("MERLIN_W"); if (env) W = atoi(env); }
    long long maxbits = (long long)MAX_MSG_LEN * 6;
    if (W > maxbits) W = (int)maxbits;
    if (W < 6) W = 6;

    auto send_offset = [&](int p) { return (int)((long long)W * (p - 1) / senders); };

    if (AGENT_ID != 0) {
        auto& mylist = sender_edges[AGENT_ID];
        int cnt = (int)mylist.size();
        int off = send_offset(AGENT_ID);
        vector<int> bits;
        for (int i = 0; i < cnt; i++) {
            int res = probe(mylist[i].r, mylist[i].c);
            bits.push_back(res);
            if ((i + 1 + off) % W == 0 || i == cnt - 1) {
                string s;
                for (size_t b = 0; b < bits.size(); b += 6) {
                    int val = 0;
                    for (int j = 0; j < 6 && b + j < bits.size(); j++)
                        if (bits[b + j]) val |= (1 << j);
                    s += to_b64(val);
                }
                send_msg(0, s);
                bits.clear();
            }
        }
        do_halt();
    }

    // ---------- agent 0: brain ----------
    status_e.assign(E, -1);
    dsu_p.resize(V);
    for (int i = 0; i < V; i++) dsu_p[i] = i;
    room_edges.assign(V, {});
    for (auto& e : edges_by_id) { room_edges[e.u].push_back(e.id); room_edges[e.v].push_back(e.id); }

    // simulate sender schedules: for each send, (send_round, sender, nbits)
    struct SendEv { int round, sender, nbits; };
    vector<SendEv> sends;
    for (int p = 1; p < NUM_AGENTS; p++) {
        int cnt = (int)sender_edges[p].size();
        int off = send_offset(p);
        int rnd = 0, pend = 0;
        for (int i = 0; i < cnt; i++) {
            rnd++; pend++;                 // probe round
            if ((i + 1 + off) % W == 0 || i == cnt - 1) {
                rnd++;                     // send round
                sends.push_back({rnd, p, pend});
                pend = 0;
            }
        }
    }
    sort(sends.begin(), sends.end(), [](const SendEv&a, const SendEv&b){ return a.round < b.round; });
    int total_msgs = (int)sends.size();

    // per-edge: round at which agent 0 will learn it from a sender (INF if never)
    vector<int> arrival_round(E, 1 << 29);
    for (int p = 1; p < NUM_AGENTS; p++) {
        int cnt = (int)sender_edges[p].size();
        int off = send_offset(p);
        int rnd = 0;
        vector<int> pendIdx;
        for (int i = 0; i < cnt; i++) {
            rnd++;
            pendIdx.push_back(i);
            if ((i + 1 + off) % W == 0 || i == cnt - 1) {
                rnd++;
                for (int idx : pendIdx) arrival_round[sender_edges[p][idx].id] = rnd + 1;
                pendIdx.clear();
            }
        }
    }
    int PEN = 0, LOOK = 2 * W;
    { const char* e1 = getenv("MERLIN_PEN"); if (e1) PEN = atoi(e1);
      const char* e2 = getenv("MERLIN_LOOK"); if (e2) LOOK = atoi(e2); }

    vector<int> decode_idx(NUM_AGENTS, 0);   // per-sender next edge index in their list
    vector<int> msg_idx_per_sender(NUM_AGENTS, 0); // how many msgs decoded from sender
    // per-sender chunk sizes in order
    vector<vector<int>> chunk_bits(NUM_AGENTS);
    for (auto& se : sends) chunk_bits[se.sender].push_back(se.nbits);

    int my_round = 0;      // rounds agent 0 has completed
    int msgs_read = 0;
    int send_ptr = 0;      // pointer into sends[] for availability
    int avail = 0;
    vector<int> distS, distG;

    infer();
    try_claim();

    while (true) {
        // availability: message sent at round r readable at rounds > r
        while (send_ptr < total_msgs && sends[send_ptr].round <= my_round) { avail++; send_ptr++; }

        if (msgs_read < avail) {
            string res = recv_any();
            my_round++;
            if (!res.empty()) {
                size_t sp = res.find(' ');
                int sender = stoi(res.substr(0, sp));
                string body = res.substr(sp + 1);
                msgs_read++;
                int nbits = chunk_bits[sender][msg_idx_per_sender[sender]++];
                int bi = 0;
                for (char ch : body) {
                    int val = from_b64(ch);
                    for (int j = 0; j < 6 && bi < nbits; j++, bi++) {
                        int eidx = decode_idx[sender]++;
                        int id = sender_edges[sender][eidx].id;
                        int st = (val >> j) & 1;
                        if (status_e[id] == -1) set_status(id, st); else g_redundant++;
                        g_decoded++;
                    }
                }
                infer();
                try_claim();
            }
            continue;
        }

        // adaptive probe: bidirectional frontier
        int rootS = dsu_find(0), rootG = dsu_find(V - 1);
        comp_bfs(rootS, distS);
        comp_bfs(rootG, distG);
        int best = -1, bestPr = 1 << 30;
        for (int id = 0; id < E; id++) {
            if (status_e[id] != -1) continue;
            int ru = dsu_find(edges_by_id[id].u), rv = dsu_find(edges_by_id[id].v);
            int pr = 1 << 29;
            if (ru == rootS || rv == rootS) {
                int w = (ru == rootS) ? edges_by_id[id].v : edges_by_id[id].u;
                int d = distG[w];
                if (d >= 0 && d < pr) pr = d;
            }
            if (ru == rootG || rv == rootG) {
                int w = (ru == rootG) ? edges_by_id[id].v : edges_by_id[id].u;
                int d = distS[w];
                if (d >= 0 && d < pr) pr = d;
            }
            if (pr < (1 << 28) && arrival_round[id] <= my_round + LOOK) pr += PEN;
            if (pr < bestPr) { bestPr = pr; best = id; }
        }
        if (best < 0) {
            // fallback: any unknown edge
            for (int id = 0; id < E; id++) if (status_e[id] == -1) { best = id; break; }
        }
        if (best < 0) {
            // everything known but no claim?? shouldn't happen
            cout << "." << endl; string ok; cin >> ok; my_round++;
            continue;
        }
        int res = probe(edges_by_id[best].r, edges_by_id[best].c);
        my_round++;
        set_status(best, res);
        infer();
        try_claim();
    }
    return 0;
}
