#include <bits/stdc++.h>

using namespace std;
#define _overload3(_1,_2,_3,name,...) name
#define _rep(i,n) repi(i,0,n)
#define repi(i,a,b) for(int i=int(a);i<int(b);++i)
#define rep(...) _overload3(__VA_ARGS__,repi,_rep,)(__VA_ARGS__)

typedef long long ll;
const ll INF = 1LL<<60;

ll gcd(ll a, ll b) {
  if(b == 0) return a;
  return gcd(b, a % b);
}

template<class T> inline bool chmin(T& a, T b) { if (a > b) { a = b; return true; } return false; }
template<class T> inline bool chmax(T& a, T b) { if (a < b) { a = b; return true; } return false; }

ll modpow(ll a, ll n, ll mod) {
  ll res = 1;
  while (n > 0) {
    if (n & 1) res = res * a % mod;
    a = a * a % mod;
    n >>= 1;
  }
  return res;
}

#define MAX_V 100100

struct edge {
    int to;
    int cost;
};

// <最短距離, 頂点の番号>
using P = pair<int, int>;

int V;
vector<edge> G1[MAX_V];
vector<edge> G2[MAX_V];

ll d1[MAX_V];
ll d2[MAX_V];

void dijkstra1(int s) {
    priority_queue<P, vector<P>, greater<P> > que;
    fill(d1, d1+V, INF);
    d1[s] = 0;
    que.push(P(0, s));

    while (!que.empty()) {
        P p = que.top();
        que.pop();
        int v = p.second;
        if (d1[v] < p.first) continue;

        for (int i=0; i<G1[v].size(); ++i) {
            edge e = G1[v][i];
            if (d1[e.to] > d1[v] + e.cost) {
                d1[e.to] = d1[v] + e.cost;
                que.push(P(d1[e.to], e.to));
            }
        }
    }
}

void dijkstra2(int s) {
    priority_queue<P, vector<P>, greater<P> > que;
    fill(d2, d2+V, INF);
    d2[s] = 0;
    que.push(P(0, s));

    while (!que.empty()) {
        P p = que.top();
        que.pop();
        int v = p.second;
        if (d2[v] < p.first) continue;

        for (int i=0; i<G2[v].size(); ++i) {
            edge e = G2[v][i];
            if (d2[e.to] > d2[v] + e.cost) {
                d2[e.to] = d2[v] + e.cost;
                que.push(P(d2[e.to], e.to));
            }
        }
    }
}

// ただのセグメント木 (区間最小, long long)
struct SegmentTree {
private:
  int n;
  vector<ll> node;

public:
    // 元配列 v をセグメント木で表現する
  SegmentTree(vector<ll> v) {
    // 最下段のノード数は元配列のサイズ以上になる最小の 2 冪 -> これを n とおく
    // セグメント木全体で必要なノード数は 2n-1 個である
    int sz = v.size();
    n = 1; while(n < sz) n *= 2;
    node.resize(2*n-1, INF); // 最小値は-1

    // 最下段に値を入れたあとに、下の段から順番に値を入れる
    // 値を入れるには、自分の子の 2 値を参照すれば良い
    for(int i=0; i<sz; i++) node[i+n-1] = v[i];
    for(int i=n-2; i>=0; i--) node[i] = min(node[2*i+1], node[2*i+2]);
  }

  void update(int x, ll val) {
    // 最下段のノードにアクセスする
    x += (n - 1);

    // 最下段のノードを更新したら、あとは親に上って更新していく
    node[x] = val;
    while(x > 0) {
      x = (x - 1) / 2;
      node[x] = min(node[2*x+1], node[2*x+2]);
    }
  }

    // 要求区間 [a, b) 中の要素の最大値を答える
    // k := 自分がいるノードのインデックス
    // 対象区間は [l, r) にあたる
  ll getmin(int a, int b, int k=0, int l=0, int r=-1) {
    // 最初に呼び出されたときの対象区間は [0, n)
    if(r < 0) r = n;

    // 要求区間と対象区間が交わらない -> 適当に返す
    if(r <= a || b <= l) return INF;

    // 要求区間が対象区間を完全に被覆 -> 対象区間を答えの計算に使う
    if(a <= l && r <= b) return node[k];

    // 要求区間が対象区間の一部を被覆 -> 子について探索を行う
    // 左側の子を vl ・ 右側の子を vr としている
    // 新しい対象区間は、現在の対象区間を半分に割ったもの
    ll vl = getmin(a, b, 2*k+1, l, (l+r)/2);
    ll vr = getmin(a, b, 2*k+2, (l+r)/2, r);
    return min(vl, vr);
  }
};

int main(){
  cin.tie(0);
  ios::sync_with_stdio(false);

  int m, s, t;
  cin >> V >> m >> s >> t;

  for(int i = 0; i < m; i++){
    int u, v, a, b;
    cin >> u >> v >> a >> b;
    u--;
    v--;
    edge e1 = {v, a};
    edge e2 = {u, a};
    G1[u].push_back(e1);
    G1[v].push_back(e2);

    edge e3 = {v, b};
    edge e4 = {u, b};
    G2[u].push_back(e3);
    G2[v].push_back(e4);

  }

  s--;
  t--;
  dijkstra1(s);
  dijkstra2(t);

  vector<ll> cost(V);
  int n = V;
  rep(i, n){
    cost[i] = d1[i] + d2[i];
  }

  vector<ll> ans(n);

  SegmentTree sg(cost);

  rep(i, n){
    ans[i] = sg.getmin(0, n);
    sg.update(i, INF);
  }

  rep(i, n){
    cout << setprecision(20) << 1e15 - ans[i] << endl;
  }




  return 0;
}


Measured memory_bytes: 19144