#include <algorithm>
#include <cmath>
#include <functional>
#include <iomanip>
#include <iostream>
#include <limits>
#include <map>
#include <queue>
#include <set>
#include <tuple>
#include <vector>

using namespace std;

#define rep(i, n) for (int64_t i = 0; i < (n); i++)
#define irep(i, n) for (int64_t i = 0; i <= (n); i++)
#define rrep(i, n) for (int64_t i = (n)-1; i >= 0; i--)
#define rirep(i, n) for (int64_t i = n; i >= 0; i--)

class FlowNetwork {
  struct Edge {
    int to;
    int64_t capacity;
  };

  const int source, sink;
  int64_t flowCache;
  vector<vector<size_t>> outEdge;
  vector<Edge> edgeData;

 public:
  FlowNetwork(int size, int source, int sink)
      : outEdge(size), source(source), sink(sink), flowCache(0) {}

  void addEdge(const int from, const int to, const int64_t capacity) {
    const size_t eId = edgeData.size();
    Edge eData1{to, capacity};
    edgeData.push_back(eData1);
    outEdge[from].push_back(eId);

    Edge eData2{from, 0};
    edgeData.push_back(eData2);
    outEdge[to].push_back(eId + 1);
  }

  void fordFulkerson() {
    const size_t n = outEdge.size();

    while (true) {
      // check if reachable to sink
      vector<int> parent(n, -1);
      queue<size_t> q;
      q.push(source);

      while (!q.empty()) {
        const size_t v = q.front();
        q.pop();

        for (const size_t eId : outEdge[v]) {
          const Edge& data = edgeData[eId];
          const size_t nv = data.to;
          if (parent[nv] < 0 && data.capacity > 0) {
            q.push(nv);
            parent[nv] = eId;
          }
        }
      }

      if (parent[sink] < 0) {
        break;
      }

      // select path from source to sink
      int64_t f = numeric_limits<int64_t>::max();
      int v = sink;
      while (v != source) {
        const int eId = parent[v];
        f = min(f, edgeData[eId].capacity);
        v = edgeData[eId ^ 1].to;
      }

      // update flow on the path
      v = sink;
      while (v != source) {
        const int eId = parent[v];
        edgeData[eId].capacity -= f;
        edgeData[eId ^ 1].capacity += f;
        v = edgeData[eId ^ 1].to;
      }
      flowCache += f;
    }
  }

  void dinic() {
    const size_t n = outEdge.size();

    while (true) {
      // check if reachable to sink
      vector<int> dist(n, numeric_limits<int>::max());
      dist[source] = 0;
      queue<size_t> q;
      q.push(source);

      while (!q.empty()) {
        const size_t v = q.front();
        q.pop();

        for (const size_t eId : outEdge[v]) {
          const Edge& data = edgeData[eId];
          const size_t nv = data.to;
          if (dist[nv] > dist[v] && data.capacity > 0) {
            q.push(nv);
            dist[nv] = dist[v] + 1;
          }
        }
      }

      if (dist[sink] == numeric_limits<int>::max()) {
        break;
      }

      // select path from source to sink
      vector<size_t> last(n, 0);
      flowCache +=
          fill_augpath(dist, last, source, numeric_limits<int64_t>::max());
    }
  }

  int64_t maxflow() const { return flowCache; }

 private:
  int64_t fill_augpath(const vector<int>& dist, vector<size_t>& last, int v,
                       int64_t f) {
    if (v == sink) {
      return f;
    }

    int64_t sum = 0;
    const vector<size_t>& es = outEdge[v];
    for (size_t& i = last[v]; i < es.size(); i++) {
      Edge& e = edgeData[es[i]];
      Edge& re = edgeData[es[i] ^ 1];
      if (dist[v] < dist[e.to] && e.capacity > 0) {
        const int64_t df =
            fill_augpath(dist, last, e.to, min(f - sum, e.capacity));
        e.capacity -= df;
        re.capacity += df;
        sum += df;
        if (sum == f) {
          break;
        }
      }
    }
    return sum;
  }
};

int main() {
  int n, m;
  cin >> n >> m;

  FlowNetwork net(n, 0, n - 1);

  rep(i, m) {
    int u, v, c;
    cin >> u >> v >> c;

    net.addEdge(u, v, c);
  }

  net.dinic();
  cout << net.maxflow() << endl;

  return 0;
}


Measured memory_bytes: 3212