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

static inline string readLine() {
    string s;
    if (!std::getline(cin, s)) { return string(); }
    while (!s.empty() && (s.back()=='\n'||s.back()=='\r')) s.pop_back();
    return s;
}
static inline void sendLine(const string& s){ cout<<s<<'\n'; cout.flush(); }

struct Edge { int r,c,a,b; };

int main(){
    long long N,NUM_AGENTS,AGENT_ID,MAX_MSG_LEN;
    { string line=readLine(); std::istringstream iss(line); iss>>N>>NUM_AGENTS>>AGENT_ID>>MAX_MSG_LEN; }
    long long m=(N+1)/2;

    vector<Edge> edges; edges.reserve((size_t)max<long long>(0,2*m*(m-1)));
    for(long long i=0;i<m;++i)for(long long j=0;j<m;++j){ int a=(int)(i*m+j);
        if(j+1<m) edges.push_back(Edge{(int)(2*i+1),(int)(2*j+2),a,(int)(i*m+(j+1))});
        if(i+1<m) edges.push_back(Edge{(int)(2*i+2),(int)(2*j+1),a,(int)((i+1)*m+j)}); }
    long long E=(long long)edges.size();

    long long headerReserve=24; long long chunkChars=MAX_MSG_LEN-headerReserve; if(chunkChars<1)chunkChars=1;
    long long chunkBitsCap=chunkChars*6;

    if(m<=1||E==0){ if(AGENT_ID==0) sendLine("! "); else sendLine("halt"); return 0; }

    // Knobs (env, for experimentation)
    auto envll=[&](const char*k,long long def)->long long{ const char*v=getenv(k); return v?atoll(v):def; };
    // MODE: 0 auto, 1 unified(all query), 2 coordinator(agent0 pure reader)
    long long MODE = envll("MODE",0);
    long long UNI_THRESH = envll("UNI_THRESH",12); // na < thresh => unified
    long long FULL_THRESH = envll("FULL_THRESH",0); // na <= this => plain full-map (0 disables)

    if(MODE==0){
        // auto: small na -> unified early-stop (agent0 also queries); large na -> coordinator.
        // crossover grows with N (empirically ~7 at N=101, ~11 at N=301, ~15 at N=501).
        long long thresh = 8 + N/40; if(thresh<4)thresh=4;
        const char* ut=getenv("UNI_THRESH"); if(ut) thresh=atoll(ut);
        MODE = (NUM_AGENTS < thresh) ? 1 : 2;
    }
    (void)UNI_THRESH;
    if(FULL_THRESH>0 && NUM_AGENTS<=FULL_THRESH) MODE=3;
    long long GREEDY_THRESH = envll("GREEDY_THRESH",0); // na<=this => MODE4 greedy(agent0 alone)
    if(GREEDY_THRESH>0 && NUM_AGENTS<=GREEDY_THRESH) MODE=4;

    // ---------- MODE 4: single-agent adaptive A* (agent 0 explores alone) ----------
    if(MODE==4){
        if(AGENT_ID!=0){ sendLine("halt"); return 0; }
        long long total=m*m; int START=0,GOAL=(int)(total-1);
        // adjacency discovered; explore rooms best-first by Manhattan dist to goal
        vector<vector<int>> adj((size_t)total);
        vector<char> visited((size_t)total,0);
        // queried edges dedup: store per room a bitmask of which of 4 dirs queried is complex; use set of edge order idx.
        // Represent edge by (min room, dir): we query wall cell. Track visited rooms; from a room query each not-yet-known neighbor.
        vector<char> qright((size_t)total,0), qdown((size_t)total,0); // queried edge to right / down neighbor (canonical from smaller id)
        auto gi=[&](int r){return r/(int)m;}; auto gj=[&](int r){return r%(int)m;};
        auto manh=[&](int r){ int i=gi(r),j=gj(r); int ti=(int)m-1,tj=(int)m-1; return (ti-i)+(tj-j); };
        // min-heap by manhattan
        priority_queue<pair<int,int>,vector<pair<int,int>>,greater<>> pq;
        vector<int> par((size_t)total,-2);
        visited[(size_t)START]=1; par[(size_t)START]=-1; pq.push({manh(START),START});
        auto queryEdge=[&](int r,int c)->int{ sendLine("? "+to_string(r)+" "+to_string(c)); string rep=readLine(); return (!rep.empty()&&rep[0]=='1')?1:0; };
        bool found=false;
        while(!pq.empty()){ int u=pq.top().second; pq.pop(); if(u==GOAL){found=true;break;} int i=gi(u),j=gj(u);
            // 4 neighbors; edge canonical stored on smaller-id endpoint
            // right neighbor (i,j+1)
            if(j+1<(int)m){ int v=u+1; if(!qright[(size_t)u]){ qright[(size_t)u]=1; if(queryEdge(2*i+1,2*j+2)){ if(!visited[(size_t)v]){visited[(size_t)v]=1;par[(size_t)v]=u;pq.push({manh(v),v});} } } }
            // left neighbor (i,j-1): edge stored on (i,j-1) as its right
            if(j-1>=0){ int v=u-1; if(!qright[(size_t)v]){ qright[(size_t)v]=1; if(queryEdge(2*i+1,2*j)){ if(!visited[(size_t)v]){visited[(size_t)v]=1;par[(size_t)v]=u;pq.push({manh(v),v});} } } }
            // down neighbor (i+1,j)
            if(i+1<(int)m){ int v=u+(int)m; if(!qdown[(size_t)u]){ qdown[(size_t)u]=1; if(queryEdge(2*i+2,2*j+1)){ if(!visited[(size_t)v]){visited[(size_t)v]=1;par[(size_t)v]=u;pq.push({manh(v),v});} } } }
            // up neighbor (i-1,j): edge stored on (i-1,j) as its down
            if(i-1>=0){ int v=u-(int)m; if(!qdown[(size_t)v]){ qdown[(size_t)v]=1; if(queryEdge(2*i,2*j+1)){ if(!visited[(size_t)v]){visited[(size_t)v]=1;par[(size_t)v]=u;pq.push({manh(v),v});} } } }
        }
        (void)found;
        vector<int> pr; for(int c=GOAL;c!=-1;c=par[(size_t)c]){pr.push_back(c); if(c==START)break;} reverse(pr.begin(),pr.end());
        string mv; for(size_t i=1;i<pr.size();++i){int d=pr[i]-pr[i-1]; if(d==1)mv+="RR";else if(d==-1)mv+="LL";else if(d==(int)m)mv+="DD";else if(d==-(int)m)mv+="UU";}
        sendLine("! "+mv); return 0;
    }

    // Build diagonal priority order (all agents identical) for MODE 1/2.
    vector<int> order;
    if(MODE==1||MODE==2){
        order.resize((size_t)E); for(long long e=0;e<E;++e) order[(size_t)e]=(int)e;
        long long cctr=2*(m-1);
        auto key=[&](int e)->long long{ const Edge&ed=edges[(size_t)e];
            int ai=ed.a/(int)m,aj=ed.a%(int)m,bi=ed.b/(int)m,bj=ed.b%(int)m;
            long long mi2=ai+bi,mj2=aj+bj; long long d=llabs(mi2-mj2); long long al=llabs(mi2+mj2-cctr);
            return d*4000000LL+al*2000LL+e; };
        sort(order.begin(),order.end(),[&](int x,int y){return key(x)<key(y);});
    }

    // ---------- MODE 3: plain full-map (baseline) ----------
    if(MODE==3){
        long long B=(E+NUM_AGENTS-1)/NUM_AGENTS; long long lo=min(E,AGENT_ID*B),hi=min(E,lo+B);
        if(AGENT_ID!=0){
            vector<char> bits((size_t)max<long long>(0,hi-lo),0);
            for(long long e=lo;e<hi;++e){const Edge&ed=edges[(size_t)e]; sendLine("? "+to_string(ed.r)+" "+to_string(ed.c)); string rep=readLine(); bits[(size_t)(e-lo)]=(!rep.empty()&&rep[0]=='1')?1:0;}
            for(long long start=lo;start<hi;start+=chunkBitsCap){ long long cnt=min(chunkBitsCap,hi-start); long long nch=(cnt+5)/6; string pk; pk.reserve((size_t)nch);
                for(long long ci=0;ci<nch;++ci){int val=0;for(int k=0;k<6;++k){long long bi=ci*6+k; if(bi<cnt&&bits[(size_t)((start-lo)+bi)])val|=(1<<k);}pk.push_back((char)(32+val));}
                sendLine("> 0 "+to_string(start)+" "+to_string(cnt)+" "+pk); readLine(); }
            sendLine("halt"); return 0;
        }
        vector<char> ee((size_t)E,0); long long filled=0;
        for(long long e=lo;e<hi;++e){const Edge&ed=edges[(size_t)e]; sendLine("? "+to_string(ed.r)+" "+to_string(ed.c)); string rep=readLine(); ee[(size_t)e]=(!rep.empty()&&rep[0]=='1')?1:0; ++filled;}
        while(filled<E){ sendLine("< ?"); string rep=readLine(); if(rep.empty()||rep[0]=='-')continue;
            size_t p1=rep.find(' '); if(p1==string::npos)continue; string body=rep.substr(p1+1);
            size_t q1=body.find(' '); if(q1==string::npos)continue; long long S=strtoll(body.substr(0,q1).c_str(),0,10);
            size_t q2=body.find(' ',q1+1); if(q2==string::npos)continue; long long cnt=strtoll(body.substr(q1+1,q2-(q1+1)).c_str(),0,10); string pk=body.substr(q2+1);
            for(long long k=0;k<cnt;++k){long long ci=k/6,kk=k%6; if((size_t)ci>=pk.size())break; int val=(int)(unsigned char)pk[(size_t)ci]-32; long long g=S+k; if(g>=0&&g<E)ee[(size_t)g]=(char)((val>>kk)&1);} filled+=cnt; }
        long long total=m*m; vector<vector<int>> adj((size_t)total);
        for(long long e=0;e<E;++e) if(ee[(size_t)e]){adj[(size_t)edges[(size_t)e].a].push_back(edges[(size_t)e].b);adj[(size_t)edges[(size_t)e].b].push_back(edges[(size_t)e].a);}
        vector<int> par((size_t)total,-2); int st=0,gl=(int)(total-1); par[(size_t)st]=-1; vector<int> q; q.push_back(st); size_t h=0;
        while(h<q.size()){int u=q[h++]; if(u==gl)break; for(int v:adj[(size_t)u])if(par[(size_t)v]==-2){par[(size_t)v]=u;q.push_back(v);}}
        vector<int> pr; for(int c=gl;c!=-1;c=par[(size_t)c]){pr.push_back(c); if(c==st)break;} reverse(pr.begin(),pr.end());
        string mv; for(size_t i=1;i<pr.size();++i){int d=pr[i]-pr[i-1]; if(d==1)mv+="RR";else if(d==-1)mv+="LL";else if(d==(int)m)mv+="DD";else if(d==-(int)m)mv+="UU";}
        sendLine("! "+mv); return 0;
    }

    // Common: stride/offset. MODE1 unified: stride=na, offset=AGENT_ID, coordinator=agent0 also queries.
    // MODE2 coordinator: workers only, stride=W=na-1, offset=AGENT_ID-1; agent0 pure reader.
    long long stride = (MODE==1)? NUM_AGENTS : (NUM_AGENTS-1);
    // chunk size in bits: default sqrt-ish but >= producers to keep coordinator synced
    long long producers = (NUM_AGENTS-1);
    long long gdef = (MODE==2) ? producers : max<long long>(producers, (long long)llround(sqrt((double)E)));
    long long g = envll("GCHUNK", gdef);
    if(g<1)g=1; if(g>chunkBitsCap)g=chunkBitsCap;

    // ---------------- WORKER ----------------
    if(!(MODE==2 && AGENT_ID==0) && !(MODE==1 && AGENT_ID==0)){
        long long offset = (MODE==1)? AGENT_ID : (AGENT_ID-1);
        vector<char> mybits; long long k=0,sentK=0;
        for(long long pos=offset;pos<E;pos+=stride){ const Edge&ed=edges[(size_t)order[(size_t)pos]];
            sendLine("? "+to_string(ed.r)+" "+to_string(ed.c)); string rep=readLine();
            mybits.push_back((!rep.empty()&&rep[0]=='1')?1:0); ++k;
            if(k-sentK>=g){ long long cnt=k-sentK; long long nch=(cnt+5)/6; string pk; pk.reserve((size_t)nch);
                for(long long ci=0;ci<nch;++ci){int val=0;for(int b=0;b<6;++b){long long bi=ci*6+b; if(bi<cnt&&mybits[(size_t)(sentK+bi)])val|=(1<<b);}pk.push_back((char)(32+val));}
                sendLine("> 0 "+to_string(sentK)+" "+to_string(cnt)+" "+pk); readLine(); sentK=k; } }
        if(k-sentK>0){ long long cnt=k-sentK; long long nch=(cnt+5)/6; string pk; pk.reserve((size_t)nch);
            for(long long ci=0;ci<nch;++ci){int val=0;for(int b=0;b<6;++b){long long bi=ci*6+b; if(bi<cnt&&mybits[(size_t)(sentK+bi)])val|=(1<<b);}pk.push_back((char)(32+val));}
            sendLine("> 0 "+to_string(sentK)+" "+to_string(cnt)+" "+pk); readLine(); }
        sendLine("halt"); return 0;
    }

    // ---------------- AGENT 0 (coordinator, possibly also querier) ----------------
    long long total=m*m;
    vector<int> uf((size_t)total); for(long long i=0;i<total;++i)uf[(size_t)i]=(int)i;
    function<int(int)> find=[&](int x){ while(uf[(size_t)x]!=x){uf[(size_t)x]=uf[(size_t)uf[(size_t)x]];x=uf[(size_t)x];} return x; };
    vector<vector<int>> adj((size_t)total);
    int START=0,GOAL=(int)(total-1); bool connected=false;
    auto addEdgePos=[&](long long pos){ const Edge&ed=edges[(size_t)order[(size_t)pos]];
        adj[(size_t)ed.a].push_back(ed.b); adj[(size_t)ed.b].push_back(ed.a);
        int ra=find(ed.a),rb=find(ed.b); if(ra!=rb)uf[(size_t)ra]=rb; };
    auto processMsg=[&](const string& rep){ size_t p1=rep.find(' '); if(p1==string::npos)return; long long sender=strtoll(rep.substr(0,p1).c_str(),0,10); long long off=(MODE==1)?sender:(sender-1); string body=rep.substr(p1+1);
        size_t q1=body.find(' '); if(q1==string::npos)return; long long k0=strtoll(body.substr(0,q1).c_str(),0,10);
        size_t q2=body.find(' ',q1+1); if(q2==string::npos)return; long long cnt=strtoll(body.substr(q1+1,q2-(q1+1)).c_str(),0,10); string pk=body.substr(q2+1);
        for(long long t=0;t<cnt;++t){ long long ci=t/6,kk=t%6; if((size_t)ci>=pk.size())break; int val=(int)(unsigned char)pk[(size_t)ci]-32; int bit=(val>>kk)&1; long long pos=off+(k0+t)*stride; if(pos<0||pos>=E)continue; if(bit) addEdgePos(pos); } };

    // agent0 own querying (MODE 1 only)
    bool agent0Queries = (MODE==1);
    long long myoff = 0; long long mypos = agent0Queries? 0 : E; // next own position (stride na)
    // read cadence: reads must keep up with producers => read once per readEvery query-turns
    long long readEvery = agent0Queries? max<long long>(1, g/ max<long long>(1,(NUM_AGENTS-1)) ) : 1;
    long long sinceRead=0;
    long long maxAttempts = 4*E + 200000; long long attempts=0;
    while(!connected){ if(++attempts>maxAttempts)break;
        bool doQuery = agent0Queries && mypos<E && (sinceRead < readEvery);
        if(doQuery){ const Edge&ed=edges[(size_t)order[(size_t)mypos]]; sendLine("? "+to_string(ed.r)+" "+to_string(ed.c)); string rep=readLine();
            if(!rep.empty()&&rep[0]=='1') addEdgePos(mypos); mypos+=NUM_AGENTS; ++sinceRead; if(find(START)==find(GOAL))connected=true; continue; }
        // read
        sendLine("< ?"); string rep=readLine(); sinceRead=0; if(rep.empty()||rep[0]=='-'){ if(!agent0Queries||mypos>=E){ /*nothing to do but wait*/ } continue; }
        processMsg(rep); if(find(START)==find(GOAL))connected=true; }

    vector<int> par((size_t)total,-2); par[(size_t)START]=-1; { vector<int> q; q.push_back(START); size_t h=0; while(h<q.size()){int u=q[h++]; if(u==GOAL)break; for(int v:adj[(size_t)u])if(par[(size_t)v]==-2){par[(size_t)v]=u;q.push_back(v);} } }
    vector<int> pr; for(int c=GOAL;c!=-1;c=par[(size_t)c]){pr.push_back(c); if(c==START)break;} reverse(pr.begin(),pr.end());
    string mv; for(size_t i=1;i<pr.size();++i){int d=pr[i]-pr[i-1]; if(d==1)mv+="RR";else if(d==-1)mv+="LL";else if(d==(int)m)mv+="DD";else if(d==-(int)m)mv+="UU";}
    sendLine("! "+mv); return 0;
}
