#include <algorithm>
#include <iostream>
#include <string>
#include <tuple>
#include <vector>
using namespace std;

// 問題名："The Most Powerful Spell"
// https://onlinejudge.u-aizu.ac.jp/problems/1169
// 所要時間… 考察：30分程度 実装：20分程度

/*
***方針***
Bellman-Ford法を使いたくなるが、スタート（空文字列）から後ろに文字列を追加していくのはうまく行かない（「a<bならばa+c<b+c」が成り立たないため e.g. a="a", b="aa", c="b"）。なので、逆にゴールから先頭に文字を付け加えていく（「a<bならばc+a<c+b」は成り立つ）。
各点で、「そこからゴールまでの最強呪文（暫定）」を保持。はじめはinf="zzzzz.....zzzz（十分長い）"にしておく。
負閉路のようなもの、すなわち一周たどると辞書順が短くなる閉路が発生しない場合は、通常のBellman-Ford法のようにn回のループで最強呪文が確定する。ただし、通常のように負閉路検出時は"NO"というわけにはいかない（例えば、"bc"<"bbc"<"bbbc"<"bbbbc"<...という負のループが発生していても、これが"a"より小さくなることはない。
ループに入った文字列は明らかに答えにならないから、答えの文字列は最大でも6*n文字であるので、十分大きい回数（ループに入ってしまった文字列は6*n文字を超えるまで）Bellman-Ford法のループを行って、スタート地点における文字列がinf、または6*n文字より長ければ"NO"。それ未満ならその文字列を出力する。

***学んだこと***
dfsするのは簡単だが、その結果から復元するのは案外難しい（というよりむしろ、「ドアの数」と「接続先との相対位置」と順番という微妙な情報だけで復元できるのが面白い）。
*/

int main() {
  // 変数、infを定義
  int n, a, s, g;
  const string inf = string(100000, 'z');

  // テストケースが終わるまでループ
  while (cin >> n >> a >> s >> g, n != 0) {
    vector<string> dist(n, inf);  // 各点からゴールまでの暫定最強呪文
    dist[g] = "";  // 後ろから構築
    vector<tuple<int, int, string>> edge(a);  // 辺の情報を保持するvector（始点、終点、文字列）

    string lab;
    for (int i = 0; i < a; i++) {
      cin >> get<0>(edge[i]) >> get<1>(edge[i]) >> get<2>(edge[i]);
    }
    for (int i = 0; i < 6 * n + 10; i++) {
      for (tuple<int, int, string> e : edge) {
        if (dist[get<1>(e)] == inf) continue;
        dist[get<0>(e)] = min(dist[get<0>(e)], get<2>(e) + dist[get<1>(e)]);
      }
    }

    string ret = dist[s];

    if (dist[s].length() > 6 * n)
      cout << "NO" << endl;
    else
      cout << dist[s] << endl;
  }
}
