import std.stdio;
import std.string;
import std.conv;
import std.typecons;
import std.algorithm;
import std.functional;
import std.bigint;
import std.numeric;
import std.array;
import std.math;
import std.range;
import std.container;

string INF = "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz";
void main() {
    string[] input;
    while(true) {
        input = readln.split;
        if (input == ["0", "0", "0", "0"]) break;

        int n = input[0].to!int;
        int a = input[1].to!int;
        int s = input[2].to!int;
        int g = input[3].to!int;

        Node[] nodes = new Node[n];
        foreach(int i; 0..n) {
            nodes[i] = new Node(i);
        }
        foreach(int i; 0..a) {
            input = readln.split;
            nodes[input[0].to!int].edges ~= Edge(input[1].to!int, input[2]);
        }
        foreach(int i; 0..n) {
            nodes[i].edges.sort!("a.cost<b.cost");
        }
        nodes[s].cost = "";
        auto rbtree = redBlackTree!("a.cost==b.cost ? a.id<b.id : a.cost<b.cost", Node)(nodes[s]);
        while(!rbtree.empty) {
            Node node = rbtree.front;
            node.done = true;
            rbtree.removeFront;
            Edge[] edges = node.edges;
            for (int i=0; i<edges.length; i++) {
                if (nodes[edges[i].to].done) break;
                rbtree.removeKey(nodes[edges[i].to]);
                nodes[edges[i].to].cost = min(nodes[edges[i].to].cost, node.cost~edges[i].cost);
                rbtree.insert(nodes[edges[i].to]);
            }
        }
        writeln(nodes[g].cost == INF ? "NO":nodes[g].cost);
    }
}
class Node {
    int id;
    string cost;
    Edge[] edges;
    bool done;
    this(int id) {
        this.id = id;
        cost = INF;
        done = false;
    }
    override string toString() const {return ""; }
}
struct Edge {
    int to;
    string cost;
    bool passed;
    this(int to, string cost) {
        this.to = to;
        this.cost = cost;
        passed = false;
    }
}