// Datacenter Imprisonment - v3: parallel blind connector split + small-K hybrid DFS.
//
// Maze structure (merlin's maze.rs make_maze):
//   - N odd, rooms at odd-odd cells are ALWAYS empty; only "connector" cells
//     (exactly one even coordinate, i.e. r+c odd) are unknown; W = 2m(m-1),
//     m = (N+1)/2. Empty cells form a spanning tree of the m*m rooms.
//
// Score metric = round number of the claim (all alive agents act each round).
//
// Strategy:
//   - Deterministically split the W connectors into contiguous chunks among
//     the workers (ids 1..K-1); in BLIND mode (large K) the master also takes
//     a tail chunk.
//   - Workers query their chunk 1 cell/round and stream results to master as
//     bit-packed messages (6 bits/char in '0'..'o', <= MAX_MSG_LEN chars, so
//     6*ML results per message; remainder-sized message first), then halt.
//   - Master interleaves: receive when a message is scheduled to be readable
//     (the whole schedule is a deterministic function of (N,K,ML), identical
//     in every process), otherwise query. It claims as soon as known-open
//     edges connect room (0,0) to room (m-1,m-1): any path in a subgraph of
//     a tree IS the unique tree path.
//   - BLIND mode (K > HYBRID_K): master queries its own tail chunk. Claim
//     round ~ W/K + tail.
//   - HYBRID mode (K <= HYBRID_K): workers cover the connector list in
//     REVERSED order (goal side first) and split all of W; the master spends
//     its free rounds on a goal-biased DFS from the start room over the
//     partially-known graph (known-open edges traversed for free, unknown
//     frontier connectors queried, known walls pruned). The DFS front and the
//     workers' bottom-up coverage meet in the middle, typically well before
//     full coverage.
//   - The planned coverage-completion round C is minimized by binary search
//     over an exact simulation of the whole schedule.
//   - Fallback: if an expected message never arrives (worker death), master
//     switches to querying all still-unknown connectors itself while still
//     draining surviving workers.

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

static int N, K, ID, ML;
static int m;             // rooms per side
static long long W;       // number of connectors
static long long B;       // bits (results) per message = 6*ML
static bool hybrid;       // small-K mode

static const int HYBRID_K = 10;  // use hybrid DFS mode when K <= this

static vector<pair<int, int>> conns;  // canonical connector order (row-major)
static vector<long long> rowStart;    // canonical index of first connector in row r

static void wline(const char* s) {
    fputs(s, stdout);
    fputc('\n', stdout);
    fflush(stdout);
}

// chunk position -> canonical connector index (hybrid: reversed order)
static inline long long permAt(long long pos) { return hybrid ? W - 1 - pos : pos; }

// ---------- deterministic schedule ----------

// rounds used by a worker with share s: s queries + ceil(s/B) sends
static long long roundsFor(long long s) {
    return s == 0 ? 0 : s + (s + B - 1) / B;
}
// max share s with roundsFor(s) <= F
static long long capFor(long long F) {
    if (F < 2) return 0;
    long long k = (F + B) / (B + 1);  // ceil(F/(B+1))
    long long s = F - k;
    if (s < 0) s = 0;
    while (s > 0 && roundsFor(s) > F) s--;
    return s;
}
// send rounds for share s, remainder-first packing. returns send-round list.
static void sendRounds(long long s, vector<long long>& out) {
    out.clear();
    if (s == 0) return;
    long long t = 0, rem = s, block = (s - 1) % B + 1;
    while (rem > 0) {
        t += block;
        rem -= block;
        t += 1;  // the send turn
        out.push_back(t);
        block = B;
    }
}

struct Sched {
    long long C = 0;                        // planned full-coverage round
    vector<long long> shares;               // workers 1..K-1
    long long masterShare = 0;
    vector<pair<long long, int>> arrivals;  // (readable round, sender), sorted
};

static bool buildSched(long long C, Sched& out) {
    int nw = K - 1;
    out.shares.assign(max(nw, 0), 0);
    long long rem = W;
    for (int w = 1; w <= nw; w++) {
        long long F = C - 1 - w;  // worker w's last-action horizon
        long long s = min(capFor(F), rem);
        out.shares[w - 1] = s;
        rem -= s;
    }
    out.arrivals.clear();
    vector<long long> sr;
    for (int w = 1; w <= nw; w++) {
        sendRounds(out.shares[w - 1], sr);
        for (long long t : sr) out.arrivals.push_back({t + 1, w});
    }
    sort(out.arrivals.begin(), out.arrivals.end());
    // exact master simulation over rounds 1..C-1
    size_t ai = 0;
    long long own = 0;
    for (long long t = 1; t <= C - 1; t++) {
        if (ai < out.arrivals.size() && out.arrivals[ai].first <= t)
            ai++;
        else
            own++;
    }
    if (ai < out.arrivals.size()) return false;  // messages not drained
    if (hybrid) {
        if (rem > 0) return false;  // workers must cover everything
    } else {
        if (own < rem) return false;  // master can't cover the rest
    }
    out.masterShare = rem;
    out.C = C;
    return true;
}

static Sched planSchedule() {
    Sched s;
    long long lo = 2, hi = 2 * W + K + 10;
    while (lo < hi) {
        long long mid = (lo + hi) / 2;
        Sched tmp;
        if (buildSched(mid, tmp))
            hi = mid;
        else
            lo = mid + 1;
    }
    while (!buildSched(lo, s)) lo++;  // guard against non-monotonicity
    return s;
}

// ---------- room graph ----------

struct Dsu {
    vector<int> p;
    void init(int n) {
        p.resize(n);
        iota(p.begin(), p.end(), 0);
    }
    int find(int x) {
        while (p[x] != x) x = p[x] = p[p[x]];
        return x;
    }
    void uni(int a, int b) { p[find(a)] = find(b); }
};

// connector canonical idx -> (roomA, roomB, dir char from A to B)
static inline void edgeOf(long long idx, int& a, int& b, char& d) {
    int r = conns[idx].first, c = conns[idx].second;
    if (r % 2 == 0) {  // vertical, rooms above/below
        int i = r / 2 - 1, j = (c - 1) / 2;
        a = i * m + j;
        b = (i + 1) * m + j;
        d = 'D';
    } else {  // horizontal
        int i = (r - 1) / 2, j = c / 2 - 1;
        a = i * m + j;
        b = i * m + (j + 1);
        d = 'R';
    }
}

// connector cell (r,c) -> canonical index
static inline long long connIdxOf(int r, int c) {
    return rowStart[r] + (r % 2 ? (c - 2) / 2 : (c - 1) / 2);
}

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr);
    if (!(cin >> N >> K >> ID >> ML)) return 0;

    m = (N + 1) / 2;
    if (m == 1) {  // N == 1: start == goal
        if (ID == 0)
            wline("! ");
        else
            wline("halt");
        return 0;
    }

    conns.reserve((size_t)2 * m * (m - 1));
    rowStart.assign(N + 2, 0);
    for (int r = 1; r <= N; r++) {
        rowStart[r] = (long long)conns.size();
        for (int c = (r % 2) + 1; c <= N; c += 2)  // r+c odd
            conns.push_back({r, c});
    }
    W = (long long)conns.size();
    B = 6LL * ML;
    hybrid = (K <= HYBRID_K);

    Sched sch = planSchedule();

    // chunk offsets in PERMUTED position space:
    // worker1 [0,s1), worker2 [s1,s1+s2) ... master tail (blind mode only)
    vector<long long> off(K, 0);
    {
        long long acc = 0;
        for (int w = 1; w < K; w++) {
            off[w] = acc;
            acc += sch.shares[w - 1];
        }
        off[0] = acc;  // master chunk start
    }

    if (ID != 0) {
        // ---------------- worker ----------------
        long long s = sch.shares[ID - 1];
        if (s == 0) {
            wline("halt");
            return 0;
        }
        long long base = off[ID];
        string body, rep;
        long long block = (s - 1) % B + 1;  // remainder-first
        long long i = 0;
        while (i < s) {
            long long take = min(block, s - i);
            body.clear();
            int acc = 0, nb = 0;
            for (long long t = 0; t < take; t++, i++) {
                auto [r, c] = conns[permAt(base + i)];
                printf("? %d %d\n", r, c);
                fflush(stdout);
                if (!(cin >> rep)) return 0;
                if (rep[0] == '1') acc |= 1 << nb;
                if (++nb == 6) {
                    body.push_back((char)('0' + acc));
                    acc = 0;
                    nb = 0;
                }
            }
            if (nb) body.push_back((char)('0' + acc));
            printf("> 0 %s\n", body.c_str());
            fflush(stdout);
            if (!(cin >> rep)) return 0;  // "OK"
            block = B;
        }
        wline("halt");
        return 0;
    }

    // ---------------- master ----------------
    vector<signed char> know(W, -1);
    Dsu dsu;
    dsu.init(m * m);
    vector<unsigned char> openDR((size_t)m * m, 0);  // bit0: open Down, bit1: open Right
    const int start = 0, goal = m * m - 1;

    // hybrid DFS state
    vector<unsigned char> visited((size_t)m * m, 0);
    vector<pair<int, int>> st;  // (room, next dir idx)
    if (hybrid) {
        st.reserve((size_t)m * m);
        visited[start] = 1;
        st.push_back({start, 0});
    }

    auto applyResult = [&](long long idx, int bit) {
        if (know[idx] != -1) return;
        know[idx] = (signed char)bit;
            if (bit) {
            int a, b;
            char d;
            edgeOf(idx, a, b, d);
            dsu.uni(a, b);
            openDR[a] |= (d == 'D') ? 1 : 2;
#ifdef DFS_PUSH_WORKER_EDGES
            if (hybrid && visited[a] != visited[b]) {  // extend DFS region for free
                int v = visited[a] ? b : a;
                visited[v] = 1;
                st.push_back({v, 0});
            }
#endif
        }
    };

    // per-sender received-bit progress
    vector<long long> done(K, 0);
    size_t ai = 0;
    long long ownPtr = 0;
    bool anomaly = false;
    long long scanPtr = 0;  // fallback scan over canonical indices

    auto readMsg = [&]() -> bool {  // issue "< ?", process; false if "- -"
        wline("< ?");
        string snd, body;
        if (!(cin >> snd >> body)) exit(0);
        if (snd == "-") return false;
        int w = atoi(snd.c_str());
        if (w < 1 || w >= K) return true;  // shouldn't happen
        long long s = sch.shares[w - 1];
        long long first = (s - 1) % B + 1;
        long long nbits = (done[w] == 0) ? first : B;
        nbits = min(nbits, s - done[w]);
        long long base = off[w] + done[w];
        for (long long i = 0; i < nbits && i / 6 < (long long)body.size(); i++) {
            int v = body[i / 6] - '0';
            applyResult(permAt(base + i), (v >> (i % 6)) & 1);
        }
        done[w] += nbits;
        return true;
    };

    // goal-biased dir order: D, R, U, L
    const int di[4] = {1, 0, -1, 0};
    const int dj[4] = {0, 1, 0, -1};

    // one DFS command: returns true if it emitted a query this round
    auto dfsStep = [&]() -> bool {
        string rep;
        while (!st.empty()) {
            auto& [u, d] = st.back();
            if (u == goal) return false;  // connectivity check will claim
            if (d == 4) {
                st.pop_back();
                continue;
            }
            int dir = d++;
            int i = u / m + di[dir], j = u % m + dj[dir];
            if (i < 0 || i >= m || j < 0 || j >= m) continue;
            int v = i * m + j;
            if (visited[v]) continue;
            int r = (u / m) * 2 + 1 + di[dir], c = (u % m) * 2 + 1 + dj[dir];
            long long idx = connIdxOf(r, c);
            if (know[idx] == 0) continue;
            if (know[idx] == 1) {
                visited[v] = 1;
                st.push_back({v, 0});
                continue;
            }
            printf("? %d %d\n", r, c);
            fflush(stdout);
            if (!(cin >> rep)) exit(0);
            applyResult(idx, rep[0] == '1');
            if (know[idx] == 1 && !visited[v]) {  // applyResult may have pushed
                visited[v] = 1;
                st.push_back({v, 0});
            }
            return true;
        }
        return false;
    };

    for (long long t = 1;; t++) {
        if (dsu.find(start) == dsu.find(goal)) {
            // reconstruct: BFS over known-open edges (subgraph of the tree)
            vector<int> par((size_t)m * m, -1), pdir((size_t)m * m, 0);
            vector<int> q;
            q.push_back(start);
            par[start] = start;
            for (size_t h = 0; h < q.size() && par[goal] == -1; h++) {
                int u = q[h], i = u / m, j = u % m;
                auto push = [&](int v, char d) {
                    if (par[v] == -1) {
                        par[v] = u;
                        pdir[v] = d;
                        q.push_back(v);
                    }
                };
                if (openDR[u] & 1) push(u + m, 'D');
                if (openDR[u] & 2) push(u + 1, 'R');
                if (i > 0 && (openDR[u - m] & 1)) push(u - m, 'U');
                if (j > 0 && (openDR[u - 1] & 2)) push(u - 1, 'L');
            }
            string path;
            for (int v = goal; v != start; v = par[v]) {
                path.push_back((char)pdir[v]);
                path.push_back((char)pdir[v]);
            }
            reverse(path.begin(), path.end());
            printf("! %s\n", path.c_str());
            fflush(stdout);
            return 0;
        }

        bool pendingMsgs = false;
        for (int w = 1; w < K && !pendingMsgs; w++)
            if (done[w] < sch.shares[w - 1]) pendingMsgs = true;

        string rep;
        if (!anomaly) {
            if (ai < sch.arrivals.size() && sch.arrivals[ai].first <= t) {
                if (readMsg())
                    ai++;
                else
                    anomaly = true;  // schedule broken (worker died?)
            } else if (hybrid) {
                if (dfsStep()) continue;
                // DFS exhausted its reachable frontier this round
                if (!pendingMsgs && ai >= sch.arrivals.size()) {
                    anomaly = true;  // data missing for good; self-rescue
                    continue;        // (costs nothing: no command emitted yet)
                }
                // spend the idle round helping coverage (top-down scan)
                while (scanPtr < W && know[scanPtr] != -1) scanPtr++;
                if (scanPtr < W) {
                    auto [r, c] = conns[scanPtr];
                    printf("? %d %d\n", r, c);
                    fflush(stdout);
                    if (!(cin >> rep)) return 0;
                    applyResult(scanPtr, rep[0] == '1');
                } else {
                    wline(".");
                    if (!(cin >> rep)) return 0;
                }
            } else if (ownPtr < sch.masterShare) {
                auto [r, c] = conns[permAt(off[0] + ownPtr)];
                printf("? %d %d\n", r, c);
                fflush(stdout);
                if (!(cin >> rep)) return 0;
                applyResult(permAt(off[0] + ownPtr), rep[0] == '1');
                ownPtr++;
            } else if (pendingMsgs && ai >= sch.arrivals.size()) {
                anomaly = true;  // planned arrivals exhausted but data missing
                if (!readMsg()) { /* keep draining via fallback */ }
            } else {
                wline(".");
                if (!(cin >> rep)) return 0;
            }
        } else {
            // fallback: alternate draining survivors with self-querying unknowns
            if (pendingMsgs && (t & 1)) {
                readMsg();
            } else {
                while (scanPtr < W && know[scanPtr] != -1) scanPtr++;
                if (scanPtr < W) {
                    auto [r, c] = conns[scanPtr];
                    printf("? %d %d\n", r, c);
                    fflush(stdout);
                    if (!(cin >> rep)) return 0;
                    applyResult(scanPtr, rep[0] == '1');
                } else if (pendingMsgs) {
                    readMsg();
                } else {
                    wline(".");
                    if (!(cin >> rep)) return 0;
                }
            }
        }
    }
    return 0;
}
