#include <iostream>
#include <iomanip>
#include <string>
#include <vector>
#include <algorithm>
#include <numeric>
#include <utility>
#include <sys/time.h>
#include <cmath>
#include <tuple>
#include <queue>
#include <bitset>

using namespace std;

template<typename T>
class Dinic {
    struct edge {
        int to;
        T cap;
        int rev;
    };

    const T INF;
    vector< vector<edge> > graph;
    vector<T> min_cost;
    vector<int> iter;

    bool bfs(int s, int t) {
        min_cost.assign(graph.size(), -1);
        queue<int> que;
        min_cost[s] = 0;
        que.push(s);
        while (!que.empty()) {
            int q = que.front();
            que.pop();
            for (edge &e : graph[q]) {
                if (e.cap > 0 && min_cost[e.to] == -1) {
                    min_cost[e.to] = min_cost[q] + 1;
                    que.push(e.to);
                }
            }
        }
        return (min_cost[t] != -1);
    }

    T dfs(int idx, const int t, T flow) {
        if (idx == t) return flow;
        for (int &i = iter[idx]; i < graph[idx].size(); i++) {
            edge &e = graph[idx][i];
            if (e.cap > 0 && min_cost[idx] < min_cost[e.to]) {
                T d = dfs(e.to, t, min(flow, e.cap));
                if (d > 0) {
                    e.cap -= d;
                    graph[e.to][e.rev].cap += d;
                    return d;
                }
            }
        }
        return 0;
    }

    public:
    Dinic(int n) : INF(numeric_limits<T>::max()/100) {
        graph.resize(n);
    }

    void add_edge(int from, int to, T cap) {
        graph[from].push_back((edge){to, cap, static_cast<int>(graph[to].size())});
        graph[to].push_back((edge){from, 0, static_cast<int>(graph[from].size())-1}); //add reverse edge
    }

    T max_flow(int s, int t) {
        T flow = 0;
        while (bfs(s, t)) {
            iter.assign(graph.size(), 0);
            T f = 0;
            while ((f = dfs(s, t, INF)) > 0) {
                flow += f;
            }
        }
        return flow;
    }
};


void solve(int N, int M, int s, int t, vector< vector<int> > edges) {
    vector<bool> is_use(M);

    long long INF = numeric_limits<long long>::max()/100;

    priority_queue<pair<long long, int> > q;

    vector<vector<long long>> d(N, vector<long long>(N, INF));
    for (int i = 0; i < N; i++) {
        d[i][i] = 0;
    }
    for (int i = 0; i < M; i++) {
        int fr, to;
        fr = edges[i][0];
        to = edges[i][1];
        d[fr][to] = edges[i][2];
    }
    for (int i = 0; i < N; i++) {
        for (int j = 0; j < N; j++) {
            for (int k = 0; k < N; k++) {
                d[j][k] = min(d[j][k], d[j][i] + d[i][k]);
            }
        }
    }

    for (int i = 0; i < M; i++) {
        int fr, to;
        fr = edges[i][0];
        to = edges[i][1];
        if (d[s][to] == d[s][fr] + edges[i][2]) {
            is_use[i] = true;
        }
    }


    Dinic<long long> G = Dinic<long long>(N);
    for (int i = 0; i < M; i++) {
        int fr, to;
        fr = edges[i][0];
        to = edges[i][1];
        if (is_use[i]) {
            G.add_edge(fr, to, edges[i][3]);
        }
    }
    std::cout << G.max_flow(s, t) << std::endl;

}

int main() {
    int N, M, s, t;
    std::cin >> N >> M >> s >> t;
    s--;
    t--;
    vector< vector<int> > edges(M, vector<int>(4));
    for (int i = 0; i < M; i++) {
        for (int j = 0; j < 4; j++) {
            std::cin >> edges[i][j];
        }
        edges[i][0]--;
        edges[i][1]--;
    }

    solve(N, M, s, t, edges);

    return 0;
}



Measured memory_bytes: 3508