// Catalin Balan
// Thu Mar 25 17:29:08 EET 2010
// Flux Maxim de Cost Minim
#include <cstdio>
#include <cstdlib>
#include <vector>
#include <queue>
using namespace std;
#define NMAX 355
#define CHUNK 8192
#define INF 0x3f3f3f3f
#define FIN "fmcm.in"
#define FOUT "fmcm.out"
typedef long long i64;
char g_buf[CHUNK];
int g_p=CHUNK-1;
int Cap[NMAX][NMAX]; // Cap[i][j] = Capacitatea arcului i -> j
int Flux[NMAX][NMAX]; // Fluxul curent pe arcul i -> j
int Cost[NMAX][NMAX]; // Costul pe arcul i -> j
vector<int> G[NMAX]; // graful...
int Heap[NMAX], Poz[NMAX], L; // heapul si pozitiile in care se afla elementele in heap
int source, sink, n, m, amGasitDrum;
int Parent[NMAX]; // vector de parinti
int sum; // cand schimb costurile z cu z + dist[x] - dist[y]... un drum de la sursa la destinatie o sa difere printr-o constanta
// care initial e distanta de la sursa la destinatie
int Dist[NMAX]; // distanta de la sursa la i
bool inQ[NMAX]; // verifica daca un nod e in coada de la bellman ford
// parsare
inline int get()
{
int x = 0;
int alpha = 1;
while ((g_buf[g_p] < '0' || g_buf[g_p] > '9') && g_buf[g_p] != '-')
if (++g_p == CHUNK) fread(g_buf,1,CHUNK,stdin), g_p=0;
if (g_buf[g_p] == '-')
{
alpha = -1;
if (++g_p == CHUNK) fread(g_buf,1,CHUNK,stdin), g_p=0;
}
while (g_buf[g_p] >= '0' && g_buf[g_p] <= '9')
{
x = x*10 + g_buf[g_p]-'0';
if (++g_p == CHUNK) fread(g_buf,1,CHUNK,stdin), g_p=0;
}
return x * alpha;
}
// schimba 2 elemente din heap
void swapH( int a, int b )
{
int aux = Heap[a];
Heap[a] = Heap[b];
Heap[b] = aux;
Poz[Heap[a]] = a;
Poz[Heap[b]] = b;
}
// urca in heap
void upheap( int p )
{
while (p>>1 && Dist[Heap[p>>1]] > Dist[Heap[p]])
{
swapH(p, p>>1);
p >>= 1;
}
}
// coboara in heap
void downheap( int p)
{
int q = 0, w;
while (p != q)
{
q = p;
w = p<<1; if ( w <= L && Dist[Heap[w]] < Dist[Heap[p]]) p = w;
w = (p<<1)+1; if ( w <= L && Dist[Heap[w]] < Dist[Heap[p]]) p = w;
swapH(q, p);
}
}
// parcurgere initiala - cand am arce negative
void belmanFord()
{
int now;
queue<int> Q;
vector<int>::iterator it;
Q.push(source);
inQ[source] = true;
for (int i = 1; i <= n; ++i) Dist[i] = INF;
Dist[source] = 0;
while (!Q.empty())
{
now = Q.front();
Q.pop();
inQ[now] = false;
for (it = G[now].begin(); it != G[now].end(); ++it)
{
if ( Cap[now][*it] == 0 || Dist[*it] <= Dist[now] + Cost[now][*it] ) continue;
Dist[*it] = Dist[now] + Cost[now][*it];
if ( inQ[*it] ) continue;
Q.push(*it);
inQ[*it] = true;
}
}
sum = Dist[sink];
}
int Dijkstra()
{
vector<int>::iterator it;
// modifica costurile
for (int i = 1; i <= n; ++i)
for (it = G[i].begin(); it != G[i].end(); ++it)
if (Dist[i] != INF && Dist[*it] != INF) Cost[i][*it] += Dist[i] - Dist[*it];
// initialize
for (int i = 1; i <= n; ++i)
{
Dist[i] = INF;
Heap[i] = Poz[i] = i;
Parent[i] = 0;
}
Dist[source] = 0;
swapH(1, source);
L = n;
/*
for (int i = 1; i <= n; ++i)
{
for (int j = 1; j <= n; ++j)
fprintf(stderr, "%d ", Cap[i][j]);
fprintf(stderr, "\n");
}
fprintf(stderr, "\n");
for (int i = 1; i <= n; ++i)
{
for (int j = 1; j <= n; ++j)
fprintf(stderr, "%d ", Flux[i][j]);
fprintf(stderr, "\n");
}
fprintf(stderr, "\n");
for (int i = 1; i <= n; ++i)
{
for (int j = 1; j <= n; ++j)
fprintf(stderr, "%d ", Cost[i][j]);
fprintf(stderr, "\n");
}
*/
// fa daickstra : )
while ( L > 1 && Dist[Heap[1]] != INF)
{
int nod = Heap[1];
swapH(1, L);
downheap(1);
--L;
for (it = G[nod].begin(); it != G[nod].end(); ++it)
{
if (Cap[nod][*it] > Flux[nod][*it] && (Dist[nod] + Cost[nod][*it]) < Dist[*it])
{
Dist[*it] = Dist[nod] + Cost[nod][*it];
Parent[*it] = nod;
upheap(Poz[*it]);
}
}
}
/*
for (int i = 1; i <= n; ++i)
fprintf(stderr, "%d ", Dist[i]);
fprintf(stderr, "\n");
for (int i = 1; i <= n; ++i)
fprintf(stderr, "%d ", Parent[i]);
fprintf(stderr, "\n---------------\n");
*/
// gasit-am drum?
if (Dist[sink] != INF)
{
amGasitDrum = 1;
int fmin = INF;
for (int i = sink; i != source; i = Parent[i])
fmin = min( fmin, Cap[Parent[i]][i] - Flux[Parent[i]][i] );
for (int i = sink; i != source; i = Parent[i])
{
Flux[Parent[i]][i] += fmin;
Flux[i][Parent[i]] -= fmin;
}
sum += Dist[sink];
return fmin * sum;
}
return 0;
}
// cauta drumuri cat timp mai sunt si actualizeaza costul
i64 solve()
{
i64 rez = 0;
amGasitDrum = 1;
while (amGasitDrum)
{
amGasitDrum = 0;
rez += Dijkstra();
}
return rez;
}
int main(int argv, char ** argc)
{
freopen(FIN, "r", stdin);
freopen(FOUT, "w", stdout);
n = get();
m = get();
source = get();
sink = get();
for (;m;--m)
{
int x = get();
int y = get();
Cap[x][y] = get();
Cap[y][x] = 0;
Cost[x][y] = get();
Cost[y][x] = -Cost[x][y];
G[x].push_back(y);
G[y].push_back(x);
}
belmanFord();
printf("%lld\n", solve());
return EXIT_SUCCESS;
}