#include <algorithm>
#include <iostream>
#include <list>
#include <vector>
#include <set>
#include <string>

const std::string empty;
const std::string no("NO");

typedef int node_t;

struct arrow_t {
    node_t source;
    node_t sink;
    std::string label;
};

struct substring_t {
    std::string::const_iterator begin;
    std::string::const_iterator end;
    substring_t(const std::string& s) : begin(s.begin()), end(s.end()) {}
    int size() const { return end - begin; }
};

struct entry_t {
    substring_t label;
    std::set<node_t> nodes;
    entry_t(const substring_t& label) : label(label) {}
};

typedef std::list<entry_t> queue_t;

int compare(const substring_t& s1, const substring_t& s2) {
    for (std::string::const_iterator i1 = s1.begin, i2 = s2.begin; ; ++i1, ++i2) {
        if (i1 == s1.end) {
            return i2 == s2.end? 0 : -1;
        } else if (i2 == s2.end) {
            return 1;
        }
        int c = ((int) *i1) - ((int) *i2);
        if (c != 0) {
            return c < 0 ? -2 : 2;
        }
    }
    return 0;
}

queue_t::iterator insert(queue_t& queue, substring_t label) {
    for (queue_t::iterator it = queue.begin(); it != queue.end(); ++it) {
        switch (compare(it->label, label)) {
        case -2:
            return queue.end();
        case -1:
            label.begin += it->label.size();
            continue;
        case 0:
            return it;
        case 1:
            it->label.begin += label.size();
            return queue.insert(it, entry_t(label));
        case 2:
            return queue.insert(queue.erase(it, queue.end()), entry_t(label));
        }
    }
    return queue.insert(queue.end(), entry_t(label));
}

std::set<node_t> collect(int n, const std::vector<arrow_t>& arrows, node_t goal) {
    std::vector<std::vector<node_t> > ins(n);
    for (const arrow_t& arrow : arrows) {
        ins[arrow.sink].push_back(arrow.source);
    }

    std::set<node_t> result;
    std::vector<node_t> stack;
    stack.push_back(goal);
    while (!stack.empty()) {
        node_t node = stack.back();
        stack.pop_back();

        result.insert(node);

        for (int source : ins[node]) {
            if (result.find(source) == result.end()) {
                stack.push_back(source);
            }
        }
    }
    return result;
}

std::string calc(int n, const std::vector<arrow_t>& arrows, node_t start, node_t goal) {
    const std::set<node_t>& reachable_set = collect(n, arrows, goal);
    if (reachable_set.find(start) == reachable_set.end()) {
        return no;
    }

    size_t max = 0;
    std::vector<std::vector<const arrow_t*> > outs(n);
    for (const arrow_t& arrow : arrows) {
        if (reachable_set.find(arrow.sink) != reachable_set.end()) {
            max = std::max(max, arrow.label.size());
            outs[arrow.source].push_back(&arrow);
        }
    }

    size_t limit = n * max;

    std::string result;
    queue_t queue;
    queue.push_back(entry_t(empty));
    queue.front().nodes.insert(start);
    while (true) {
        substring_t label = queue.front().label;
        std::set<node_t> nodes = queue.front().nodes;
        queue.pop_front();

        result.append(label.begin, label.end);
        if (nodes.find(goal) != nodes.end()) {
            return result;
        }
        if (result.size() > limit) {
            return no;
        }

        for (node_t node : nodes) {
            for (const arrow_t* arrow : outs[node]) {
                queue_t::iterator it = insert(queue, arrow->label);
                if (it != queue.end()) {
                    it->nodes.insert(arrow->sink);
                }
            }
        }
    }
}

int main() {
    while (true) {
        int n, a, s, g;
        std::cin >> n >> a >> s >> g;
        if (n == 0) {
            break;
        }
        std::vector<arrow_t> arrows(a);
        for (arrow_t& arrow : arrows) {
            std::cin >> arrow.source >> arrow.sink >> arrow.label;
        }
        std::cout << calc(n, arrows, s, g) << std::endl;
    }
    return 0;
}

Measured memory_bytes: 3252