using System;
using System.Collections;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Collections.Generic;
using System.Threading.Tasks;

/*
・概要
	Dijkstra法により、始点から目的地までの最短経路を求める（コストが最小となる経路）
	負の経路がなければ、高速で最短コストを導出可能。
	PriorityQueueクラスを使用


・使用法
	1. Dijkstraクラスのオブジェクトを初期化
	引数は、（現時点での）頂点数
	(e.g. Dijkstra graph = new Dijkstra(V);）

	2. 辺を追加
	引数は、始点、終点、重み、有向グラフであるか否か
	（無向グラフの場合には、falseを入力のこと）
	（e.g. graph.AddEdge(start,goal,cost);）

	3. Dijkstra法による探索を実行
	引数は、スタート地点
	（e.g. graph.Search(R);）

	4. 任意の地点への最小コストを取得
	引数は、目的地（辿り着けない場合は、-1を返す）
	（e.g. long c = graph.GetCost(i);）

	5. 任意の地点への最短ルートを、リストで取得
	引数は、目的地
	（e.g. List<int> root = graph.GetRoot(i);）

*/

class Program
{
	//Main
	static void Main(string[] args)
	{
		//出力準備（AutoFlushを切る）
		var sw = new StreamWriter(Console.OpenStandardOutput()){AutoFlush = false};
		Console.SetOut(sw);
		//解答
		Solve();
		//出力終了
		Console.Out.Flush();
	}

	static void Solve()
	{
		//読み込む
		string[] S = Console.ReadLine().Split(' ');
		//頂点数
		int V = int.Parse(S[0]);
		//辺の数
		int E = int.Parse(S[1]);
		//スタート地点
		int R = int.Parse(S[2]);

		//1. Dijkstraクラスのオブジェクトを初期化
		//引数は、（現時点での）頂点数
		Dijkstra graph = new Dijkstra(V);

		//2. 辺を追加
		/*引数は、始点、終点、重み、有向グラフであるか否か
		（無向グラフの場合には、falseを入力のこと）*/
		//辺を読み込む
		for(int i=0; i<E; i++){
			string[] line = Console.ReadLine().Split(' ');
			int start = int.Parse(line[0]);
			int goal = int.Parse(line[1]);
			long cost = long.Parse(line[2]);

			//辺を追加
			//今回は有向グラフなので、第四引数は不要
			graph.AddEdge(start,goal,cost);
		}

		//3. Dijkstra法による探索を実行
		//引数は、スタート地点
		graph.Search(R);

		//4. 任意の地点への最小コストを取得
		//引数は、目的地
		for(int i=0; i<V; i++){
			//各地点への最小コストを取得
			/*
			たどり着くことができない場合、-1を返す
			*/
			long c = graph.GetCost(i);
			if(c < 0){
				Console.WriteLine("INF");
			}
			else{
				Console.WriteLine(c);
			}
		}

		/*
		//5. 任意の地点への最短ルートを、リストで取得
		//引数は、目的地
		for(int i=0; i<V; i++){
			//各地点への最短ルート
			List<int> root = graph.GetRoot(i);
			foreach(int n in root){
				Console.Write("{0},",n);
			}
			Console.WriteLine();
		}
		*/
	}
}


//Dijkstra法を実行するクラス
class Dijkstra{
	//PriorityQueueのインスタンス作成の際の、キャパシティ
	static readonly int capa = 100000000;

	//経路の候補を構造体で表現
	/*
	PriorityQueueに格納するのに使う。
	その為に、スタート地点からの合計コストを保持させている。
	*/
	public struct Path : IComparable<Path>
	{
		//どこから来たか
		public int From;
		//到達点たるノード番号
		public int Node;
		//（原初の）始点からの合計コスト
		public long CostSum;

		public Path(int from, int node, long cost)
		{
			this.From = from;
			this.Node = node;
			this.CostSum = cost;
		}

		//始点からの合計コストで比較
		public int CompareTo(Path other)
		{
			return this.CostSum.CompareTo(other.CostSum);
		}
	}

	//頂点を保存する構造体
	public struct Node
	{
		//ノード番号
		public int ID;
		//このノードから始まる辺のリスト
		public List<Edge> edgeList;

		public Node(int id){
			this.ID = id;
			this.edgeList = new List<Edge>();
		}
	}

	//辺を保存する構造体
	public struct Edge
	{
		//始点
		public int Start;
		//終点
		public int Goal;
		//重み
		public long Value;

		public Edge(int s, int g, long v){
			this.Start = s;
			this.Goal = g;
			this.Value = v;
		}
	}

	//頂点数と辺の数
	int V,E;
	//ノードのリスト
	List<Node> nodeList;
	//各ノードについて、最小のコストを格納
	long[] costArr;
	//各ノードについて、最短経路を取った場合に、自分の一つ前に通るべき頂点のインデックスを格納
	int[] rootArr;
	//各ノードについて、探索済みか否かを示す
	bool[] searchFlag;
	//次に進むべき経路の候補を格納
	PriorityQueue<Path> pathQueue;

	//コンストラクタ
	/*
	引数は、（現時点での）頂点数
	*/
	public Dijkstra(int vertex){
		this.V = vertex;
		this.nodeList = new List<Node>();
		for(int i=0; i<this.V; i++){
			nodeList.Add(new Node(i));
		}
		this.pathQueue = new PriorityQueue<Path>(capa,false);
	}

	//頂点を追加するメソッド
	public void AddNode(){
		Node n = new Node(V);
		//ノードのリストに追加
		this.nodeList.Add(n);
		//頂点の数を更新
		this.V ++;
	}

	//辺を追加するメソッド
	/*
	引数は、始点、終点、重み、有向グラフであるか否か
	（無向グラフの場合には、falseを入力のこと）
	*/
	public void AddEdge(int start, int goal, long value, bool directed = true){
		Edge edge = new Edge(start, goal, value);
		//始点となるノードに追加
		this.nodeList[start].edgeList.Add(edge);
		//辺の数を更新
		this.E ++;

		//無向グラフの場合、逆もやる
		if(!directed){
			Edge edge2 = new Edge(goal, start, value);
			//始点となるノードに追加
			this.nodeList[goal].edgeList.Add(edge2);
			//辺の数を更新
			this.E ++;
		}
	}

	//ダイクストラ法を実施
	/*
	引数は、始点のノード番号
	*/
	public void Search(int source){
		//初期化
		this.costArr = new long[this.V];
		//コストは-1で初期化
		for(int i=0; i<this.V; i++){
			this.costArr[i] = -1;
		}
		this.rootArr = new int[this.V];
		this.searchFlag = new bool[this.V];

		//スタート地点の処理
		searchFlag[source] = true;
		costArr[source] = 0;
		rootArr[source] = source;

		//スタート地点から繋がっている地点への経路を格納
		foreach(Edge edge in nodeList[source].edgeList){
			//経路を初期化
			Path p = new Path(edge.Start, edge.Goal, edge.Value);
			//優先度付きキューに経路を追加
			pathQueue.Enqueue(p);
		}

		//以下ループ
		//キューが空でない間
		while(pathQueue.Count > 0){
			//コストが最小となる経路を取り出す
			Path minP = pathQueue.Dequeue();
			int currentNode = minP.Node;

			//現在の値が-1である、或いは現在格納されているコストより低い場合
			if(costArr[currentNode] < 0 || minP.CostSum < costArr[currentNode]){
				//コストを更新
				costArr[currentNode] = minP.CostSum;
				//どこから来たかを更新
				rootArr[currentNode] = minP.From;

				//探索済みとする
				searchFlag[currentNode] = true;

				//次の経路を追加する処理
				foreach(Edge edge in nodeList[currentNode].edgeList){
					int nextNode = edge.Goal;
					long nextCost = edge.Value;

					//最小距離決定済みの場合には処理を飛ばす
					if(searchFlag[nextNode]){
						continue;
					}
					//未確定の場合、探索対象に追加
					else{
						long newCost = costArr[currentNode] + nextCost;
						//探索対象に追加
						Path p = new Path(currentNode, nextNode, newCost);
						pathQueue.Enqueue(p);
					}
				}
			}
		}
	}

	//特定の地点までの最短距離を取得
	/*
	引数は、目的地のノード番号
	*/
	public long GetCost(int destination){
		//到達しなかった場合は、初期値たる-1が返ってくる
		return costArr[destination];
	}

	//特定の地点までの最短ルートを、リストで取得
	/*
	引数は、目的地のノード番号
	戻り値は、通過するノード番号を始点から順番に格納したリスト
	（スタート地点自身も含む）
	*/
	public List<int> GetRoot(int destination){
		List<int> root = new List<int>();

		//到達不可能の場合、空のリストを返す
		if(costArr[destination] < 0){
			return root;
		}
		//自分自身を目的地とする点（即ち終点）に至るまで、経路を逆から追加
		else{
			int next = destination;
			root.Add(next);
			while(next != rootArr[next]){
				next = rootArr[next];
				root.Add(next);
			}

			//最後に反転させる
			root.Reverse();
		}

		return root;
	}
}


/*
優先度付きキュー
*/
class PriorityQueue<T> where T : IComparable<T>
{
    public readonly T[] Array;
    public readonly bool Greater;
    public int Count { get; private set; } = 0;

    public PriorityQueue(int capacity, bool greater = true)
    {
        this.Array = new T[capacity];
        this.Greater = greater;
    }

    public void Enqueue(T item)
    {
        this.Array[this.Count] = item;
        this.Count += 1;
        this.UpHeap();
    }

    public T Dequeue()
    {
        // 先頭要素を末尾にして再構成
        this.Swap(0, this.Count - 1);
        this.Count -= 1;
        this.DownHeap();

        return this.Array[this.Count];
    }


    private void UpHeap()
    {
        var n = this.Count - 1;
        while (n != 0)
        {
            // 親要素の座標
            var parent = (n - 1) / 2;

            if (this.Compare(this.Array[n], this.Array[parent]) > 0)
            {
                this.Swap(n, parent);
                n = parent;
            }
            else
            {
                break;
            }
        }
    }

    private void DownHeap()
    {
        var parent = 0;
        while (true)
        {
            var child = 2 * parent + 1;
            if (child > this.Count - 1) break;

            if (child < this.Count - 1 && this.Compare(this.Array[child], this.Array[child + 1]) < 0)
            {
                // 左より右の子のほうが大きい値の場合、右を入れ替え対象にする
                child += 1;
            }

            if (this.Compare(this.Array[parent], this.Array[child]) < 0)
            {
                this.Swap(parent, child);
                parent = child;
            }
            else
            {
                break;
            }
        }
    }

    private int Compare(T a, T b)
    {
        var c = a.CompareTo(b);
        if (!this.Greater)
        {
            c = -c;
        }
        return c;
    }

    private void Swap(int a, int b)
    {
        var tmp = this.Array[a];
        this.Array[a] = this.Array[b];
        this.Array[b] = tmp;
    }
}



Measured memory_bytes: 42168