#include <algorithm>
#include <cstdint>
#include <cstdlib>
#include <iostream>
#include <queue>
#include <string>
#include <utility>
#include <vector>
using namespace std;


struct Task {
    long long key1, key2;
    int v, dir;
    unsigned seq;
};
struct CmpTask { // min-heap via priority_queue
    bool operator()(Task const& a, Task const& b) const {
        if (a.key1 != b.key1) return a.key1 > b.key1;
        if (a.key2 != b.key2) return a.key2 > b.key2;
        return a.seq > b.seq;
    }
};

static inline uint64_t splitmix64(uint64_t x){
    x += 0x9e3779b97f4a7c15ULL;
    x = (x ^ (x >> 30)) * 0xbf58476d1ce4e5b9ULL;
    x = (x ^ (x >> 27)) * 0x94d049bb133111ebULL;
    return x ^ (x >> 31);
}

struct Solver {
    int N,K,id,maxmsg,m;
    int source, target;
    bool sourceIsStart;
    int strat;
    vector<int> parent, pdir, distv;
    vector<unsigned char> seen;
    // status: 0 unknown, 1 scheduled, 2 open, 3 closed
    vector<unsigned char> H, V; // H[r*(m-1)+c] edge (r,c)-(r,c+1), V[r*m+c] edge (r,c)-(r+1,c)
    priority_queue<Task, vector<Task>, CmpTask> pq;
    unsigned seq=0;
    bool found=false;
    string answer;
    int pendingV=-1, pendingDir=-1;
    bool hasPending=false;
    long long turn=0;
    static constexpr int DR[4] = {-1,0,1,0};
    static constexpr int DC[4] = {0,-1,0,1};
    static constexpr char CH[4] = {'U','L','D','R'};

    Solver(int N_,int K_,int id_,int maxmsg_):N(N_),K(K_),id(id_),maxmsg(maxmsg_) {
        m=(N+1)/2;
        int total=m*m;
        parent.assign(total,-1); pdir.assign(total,-1); distv.assign(total,0); seen.assign(total,0);
        H.assign(max(0,m*(m-1)),0); V.assign(max(0,(m-1)*m),0);
        chooseStrategy();
        source = sourceIsStart ? 0 : total-1;
        target = sourceIsStart ? total-1 : 0;
        seen[source]=1; parent[source]=source; distv[source]=0;
        addTasks(source);
        if(source==target){ found=true; answer=""; }
    }
    int idx(int r,int c) const { return r*m+c; }
    pair<int,int> rc(int v) const { return {v/m, v%m}; }
    static int invdir(int d){ return d^2; } // U<->D (0^2=2), L<->R (1^2=3)
    char invch(char ch){ if(ch=='U')return 'D'; if(ch=='D')return 'U'; if(ch=='L')return 'R'; return 'L'; }

    void chooseStrategy(){
        // Put the historically strongest/simple racers first for small K, then a portfolio.
        int s=id;
        if (s==0) { strat=0; sourceIsStart=true; return; }      // deep greedy DFS start
        if (s==1) { strat=0; sourceIsStart=false; return; }     // deep greedy DFS end
        if (s==2) { strat=1; sourceIsStart=true; return; }      // greedy best-first start
        if (s==3) { strat=1; sourceIsStart=false; return; }
        if (s==4) { strat=2; sourceIsStart=true; return; }      // weighted A*
        if (s==5) { strat=2; sourceIsStart=false; return; }
        if (s==6) { strat=3; sourceIsStart=true; return; }      // A*
        if (s==7) { strat=3; sourceIsStart=false; return; }
        int t=(s-8)%10;
        sourceIsStart = ((s-8)/10 + t) % 2 == 0;
        strat = 4 + t; // assorted biased searches
    }

    bool inb(int r,int c) const { return 0<=r && r<m && 0<=c && c<m; }
    unsigned char &edgeStatus(int r,int c,int d){
        if(d==3) return H[r*(m-1)+c];       // right
        if(d==1) return H[r*(m-1)+c-1];     // left
        if(d==2) return V[r*m+c];           // down
        return V[(r-1)*m+c];                // up
    }
    pair<int,int> queryCell(int r,int c,int d){
        // room (r,c) is grid (2r+1,2c+1), 1-indexed
        if(d==0) return {2*r,   2*c+1};
        if(d==2) return {2*r+2, 2*c+1};
        if(d==1) return {2*r+1, 2*c};
        return {2*r+1, 2*c+2};
    }
    int manh(int r,int c) const {
        auto [tr,tc]=rc(target);
        return abs(r-tr)+abs(c-tc);
    }
    long long priorityKey(int from, int d){
        auto [r,c]=rc(from);
        int nr=r+DR[d], nc=c+DC[d];
        int g=distv[from]+1;
        int h=manh(nr,nc);
        int diag = nr - nc;
        int anti = (nr + nc) - (m-1);
        uint64_t hv=splitmix64(((uint64_t)(id+1)<<48) ^ ((uint64_t)(strat+11)<<40) ^ ((uint64_t)from<<3) ^ (uint64_t)d);
        int noiseSmall = (int)(hv % 97);
        int noiseMed = (int)(hv % 1009);
        // direction tie preference: towards the target first, but vary D/R and U/L.
        int toward = h - manh(r,c); // -1 is good, +1 bad
        int dirTie=0;
        if (sourceIsStart) {
            // prefer D/R; half prefer vertical before horizontal
            int prefA[4] = {2,3,0,1};
            int prefB[4] = {3,2,1,0};
            int *p = ((id/2)&1)?prefB:prefA;
            for(int i=0;i<4;i++) if(p[i]==d) dirTie=i;
        } else {
            int prefA[4] = {0,1,2,3};
            int prefB[4] = {1,0,3,2};
            int *p = ((id/2)&1)?prefB:prefA;
            for(int i=0;i<4;i++) if(p[i]==d) dirTie=i;
        }
        long long key;
        switch(strat){
            case 0: // very deep, greedy DFS-ish
                key = -(long long)g * 1000000LL + (long long)h * 2000LL + toward*5000LL + dirTie*100LL + noiseSmall;
                break;
            case 1: // greedy best-first by Manhattan
                key = (long long)h * 1000000LL + (long long)g * 500LL + dirTie*100LL + noiseSmall;
                break;
            case 2: // strongly weighted A*
                key = ((long long)g * 1000LL + (long long)h * 5500LL) + dirTie*10LL + noiseSmall;
                break;
            case 3: // ordinary A*
                key = ((long long)g * 1000LL + (long long)h * 1000LL) + dirTie*10LL + noiseSmall;
                break;
            case 4: // beam around main diagonal, greedy
                key = (long long)h * 700000LL + (long long)abs(diag) * 180000LL + (long long)g*300LL + noiseMed;
                break;
            case 5: // favor below/right side of diagonal
                key = (long long)h * 800000LL + (long long)diag * 120000LL + (long long)g*300LL + noiseMed;
                break;
            case 6: // favor above/left side of diagonal
                key = (long long)h * 800000LL - (long long)diag * 120000LL + (long long)g*300LL + noiseMed;
                break;
            case 7: // anti-diagonal corridor
                key = (long long)h * 800000LL + (long long)abs(anti) * 100000LL + (long long)g*300LL + noiseMed;
                break;
            case 8: // moderate DFS, less depth fanatic
                key = -(long long)g * 100000LL + (long long)h * 10000LL + (long long)abs(diag)*1000LL + noiseSmall;
                break;
            case 9: // Dijkstra/BFS-ish from one end (good for short paths / traps)
                key = (long long)g * 1000000LL + (long long)h * 1000LL + noiseSmall;
                break;
            case 10: // random-biased greedy
                key = (long long)h * 1000000LL + (long long)g * 1000LL + (long long)noiseMed * 2000LL;
                break;
            case 11: // weighted A* plus diagonal side random
            default: {
                int coef = (int)((splitmix64(id*1234567ULL + 5) % 11) - 5);
                key = (long long)g * 1000LL + (long long)h * 3000LL + (long long)coef * diag * 20000LL + noiseMed;
                break;
            }
        }
        return key;
    }
    void addTasks(int v){
        auto [r,c]=rc(v);
        for(int d=0; d<4; ++d){
            int nr=r+DR[d], nc=c+DC[d];
            if(!inb(nr,nc)) continue;
            int to=idx(nr,nc);
            if(parent[v]==to) continue; // known parent edge
            unsigned char &st=edgeStatus(r,c,d);
            if(st!=0) continue;
            st=1;
            long long key=priorityKey(v,d);
            uint64_t hv=splitmix64(((uint64_t)id<<32) ^ ((uint64_t)v<<4) ^ d ^ 0xabcdefULL);
            pq.push(Task{key, (long long)(hv & 0xffffffffULL), v, d, seq++});
        }
    }
    string buildAnswer(int foundNode){
        vector<int> dirs;
        int cur=foundNode;
        while(cur!=source){
            int d=pdir[cur];
            dirs.push_back(d);
            cur=parent[cur];
        }
        string roomSteps;
        if(sourceIsStart){
            reverse(dirs.begin(), dirs.end());
            for(int d: dirs) roomSteps.push_back(CH[d]);
        }else{
            // dirs currently target(start)->... backwards? Actually collected source(end)->target(start) reversed.
            reverse(dirs.begin(), dirs.end()); // source(end) -> target(start)
            // invert and reverse to get start -> end
            for(int i=(int)dirs.size()-1;i>=0;--i) roomSteps.push_back(CH[invdir(dirs[i])]);
        }
        string ans;
        ans.reserve(roomSteps.size()*2);
        for(char ch: roomSteps){ ans.push_back(ch); ans.push_back(ch); }
        return ans;
    }
    void processReply(const string &resp){
        if(!hasPending) return;
        bool open = (!resp.empty() && resp[0]=='1');
        int v=pendingV, d=pendingDir;
        auto [r,c]=rc(v);
        unsigned char &st=edgeStatus(r,c,d);
        st = open ? 2 : 3;
        if(open){
            int nr=r+DR[d], nc=c+DC[d];
            int to=idx(nr,nc);
            // In a tree, if already seen this would imply a cycle; ignore defensively.
            if(!seen[to]){
                seen[to]=1; parent[to]=v; pdir[to]=d; distv[to]=distv[v]+1;
                if(to==target){ found=true; answer=buildAnswer(to); return; }
                // Mark reverse edge open too (same status already), then continue.
                addTasks(to);
            }
        }
        hasPending=false;
    }
    string nextCommand(){
        if(found){ return string("! ") + answer; }
        while(!pq.empty()){
            Task t=pq.top(); pq.pop();
            auto [r,c]=rc(t.v);
            int nr=r+DR[t.dir], nc=c+DC[t.dir];
            if(!inb(nr,nc)) continue;
            unsigned char &st=edgeStatus(r,c,t.dir);
            if(st!=1) continue; // stale
            auto [qr,qc]=queryCell(r,c,t.dir);
            pendingV=t.v; pendingDir=t.dir; hasPending=true;
            return string("? ") + to_string(qr) + " " + to_string(qc);
        }
        // Should be impossible in a connected maze. Stay alive if something went wrong.
        return ".";
    }
};

struct StreamScan {
    int N,K,id,L,m,E,Q,B; bool isCollector; long long block=0; int posInBlock=0; bool awaitingQuery=false, awaitingSend=false; int pendingEdge=-1; vector<unsigned char> bits;
    bool connected=false; string answer;
    struct Edge{int r,c,d;}; vector<Edge> edges;
    vector<int> dsu, dsur; vector<vector<pair<int,int>>> adj; // room graph: to, dir
    static constexpr const char* ALPH="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
    static constexpr int DR[4]={-1,0,1,0}; static constexpr int DC[4]={0,-1,0,1}; static constexpr char CH[4]={'U','L','D','R'};
    StreamScan(int N_,int K_,int id_,int L_):N(N_),K(K_),id(id_),L(L_){
        m=(N+1)/2; Q=max(1,K-1); B=56; isCollector=(id==0); buildEdges(); int total=m*m; dsu.resize(total); dsur.assign(total,0); adj.assign(total,{}); for(int i=0;i<total;i++) dsu[i]=i; bits.reserve(B);
    }
    int idx(int r,int c) const{return r*m+c;} static int invdir(int d){return d^2;}
    int find(int x){ while(dsu[x]!=x){ dsu[x]=dsu[dsu[x]]; x=dsu[x]; } return x; }
    void unite(int a,int b){ int ra=find(a),rb=find(b); if(ra==rb)return; if(dsur[ra]<dsur[rb]) swap(ra,rb); dsu[rb]=ra; if(dsur[ra]==dsur[rb]) dsur[ra]++; }
    static uint64_t smix(uint64_t x){ x+=0x9e3779b97f4a7c15ULL; x=(x^(x>>30))*0xbf58476d1ce4e5b9ULL; x=(x^(x>>27))*0x94d049bb133111ebULL; return x^(x>>31); }
    void buildEdges(){
        for(int r=0;r<m;r++) for(int c=0;c<m;c++){ if(r+1<m) edges.push_back({r,c,2}); if(c+1<m) edges.push_back({r,c,3}); }
        E=(int)edges.size(); int mm=m;
        sort(edges.begin(), edges.end(), [&](const Edge&a,const Edge&b){
            auto key=[&](const Edge&e){
                int x,y; if(e.d==2){ x=2*e.r+1; y=2*e.c; } else { x=2*e.r; y=2*e.c+1; }
                int diag=abs(x-y);
                int S=x+y;
                // A diagonal-band scan. Small deterministic noise spreads equally ranked edges a little.
                int noise=(int)(smix(((uint64_t)e.r<<32)^((uint64_t)e.c<<16)^e.d)%3);
                return tuple<int,int,int,int,int>(diag, S, noise, e.r, e.c*4+e.d);
            };
            return key(a)<key(b);
        });
    }
    pair<int,int> edgeCell(int ei){ Edge &e=edges[ei]; if(e.d==2) return {2*e.r+2,2*e.c+1}; else return {2*e.r+1,2*e.c+2}; }
    int edgePos(long long b,int t,int agent){ return (int)(b*(long long)Q*B + (long long)t*Q + (agent-1)); }
    string encodeBits(){ string s; int val=0,cnt=0; for(unsigned char bit:bits){ val|=(bit?1:0)<<cnt; cnt++; if(cnt==6){s.push_back(ALPH[val]); val=0; cnt=0;} } if(cnt) s.push_back(ALPH[val]); return s; }
    int dec(char ch){ if('A'<=ch&&ch<='Z') return ch-'A'; if('a'<=ch&&ch<='z') return 26+ch-'a'; if('0'<=ch&&ch<='9') return 52+ch-'0'; if(ch=='+') return 62; if(ch=='/') return 63; return 0; }
    void addOpenEdge(int ei){
        if(ei<0||ei>=E) return; Edge &e=edges[ei]; int u=idx(e.r,e.c); int vr=e.r+DR[e.d], vc=e.c+DC[e.d]; int v=idx(vr,vc); if(find(u)!=find(v)){ unite(u,v); adj[u].push_back({v,e.d}); adj[v].push_back({u,invdir(e.d)}); }
        if(!connected && find(0)==find(m*m-1)){ computeAnswer(); connected=true; }
    }
    void computeAnswer(){
        int total=m*m; vector<int> par(total,-1), pd(total,-1); queue<int>q; q.push(0); par[0]=0;
        while(!q.empty()){ int v=q.front();q.pop(); if(v==total-1) break; for(auto [to,d]:adj[v]) if(par[to]==-1){par[to]=v; pd[to]=d; q.push(to);} }
        vector<int> dirs; int cur=total-1; while(cur!=0 && cur>=0 && par[cur]!=-1){ dirs.push_back(pd[cur]); cur=par[cur]; }
        reverse(dirs.begin(), dirs.end()); answer.clear(); answer.reserve(dirs.size()*2); for(int d:dirs){ answer.push_back(CH[d]); answer.push_back(CH[d]); }
    }
    void processCollectorReply(const string& resp){
        if(resp.size()<3 || (resp[0]=='-' && resp[1]==' ')) return;
        size_t sp=resp.find(' '); if(sp==string::npos) return; int sender=0; for(size_t i=0;i<sp;i++) if(resp[i]>='0'&&resp[i]<='9') sender=sender*10+(resp[i]-'0'); if(sender<=0||sender>=K) return;
        string body=resp.substr(sp+1); size_t cp=body.find(':'); if(cp==string::npos) return; long long b=0; for(size_t i=0;i<cp;i++) if(body[i]>='0'&&body[i]<='9') b=b*10+(body[i]-'0');
        int t=0; for(size_t i=cp+1;i<body.size() && t<B;i++){ int val=dec(body[i]); for(int k=0;k<6 && t<B;k++,t++){ if((val>>k)&1) addOpenEdge(edgePos(b,t,sender)); } }
    }
    void processReply(const string& resp){
        if(isCollector){ processCollectorReply(resp); return; }
        if(awaitingQuery){ bits.push_back((!resp.empty()&&resp[0]=='1')?1:0); awaitingQuery=false; posInBlock++; }
        else if(awaitingSend){ awaitingSend=false; block++; posInBlock=0; bits.clear(); }
    }
    string nextCommand(){
        if(isCollector){ if(connected) return string("! ")+answer; return "< ?"; }
        if(awaitingQuery||awaitingSend) return "."; // should not happen before reply
        // If no more possible edges for this and future blocks, idle.
        int first=edgePos(block,0,id);
        if(first>=E && bits.empty()) return ".";
        if(posInBlock<B){
            int ei=edgePos(block,posInBlock,id);
            if(ei<E){ pendingEdge=ei; auto [r,c]=edgeCell(ei); awaitingQuery=true; return string("? ")+to_string(r)+" "+to_string(c); }
            else { bits.push_back(0); posInBlock++; return nextCommand(); }
        } else {
            string body=to_string(block)+":"+encodeBits(); awaitingSend=true; return string("> 0 ")+body;
        }
    }
};

int main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int N,K,id,L;
    if(!(cin>>N>>K>>id>>L)) return 0;
    string dummy; getline(cin,dummy);
    bool useStream = (K>=4);
    Solver sol(N,K,id,L);
    StreamScan ss(N,K,id,L);
    bool first=true;
    string resp;
    while(true){
        if(!first){
            if(!getline(cin, resp)) return 0;
            if(useStream) ss.processReply(resp); else sol.processReply(resp);
        }
        string cmd = useStream ? ss.nextCommand() : sol.nextCommand();
        cout << cmd << '\n' << flush;
        if(cmd.size() && cmd[0]=='!') return 0;
        first=false;
    }
}

// stream B56 diagS th4
