#include <bits/stdc++.h>

using namespace std;

namespace {

/**
 * Currently this design only allows one arena per arena size
 *
 * @tparam SIZE size of arena in bytes, default is 200 MB
 */
template <size_t SIZE = 200 << 20> class Arena {
  static char buf[];
  static size_t buf_ind;

 public:
  template <class T> struct Allocator {
    typedef T value_type;

    Allocator() {}

    template <class U> Allocator(const U&) {}

    T* allocate(size_t n) {
      buf_ind -= n * sizeof(T);
      buf_ind &= 0 - alignof(T);
      return (T*) (buf + buf_ind);
    }

    void deallocate(T*, size_t) {}
  };

  /**
   * Frees the arena, be very sure no allocation is still used when calling this
   */
  static void Clear() {
    buf_ind = SIZE;
  }
};

template <size_t SIZE> char Arena<SIZE>::buf[SIZE];
template <size_t SIZE> size_t Arena<SIZE>::buf_ind = SIZE;

#define VEVE(i, a, b) for (ll i = a, __##i = b; i < __##i; ++i)
#define DYDY(i, a, b) for (ll i = a, __##i = b; i > __##i; --i)
#define RARA(x, seq) for (auto &x : seq)
#define SIZE(x) ((ll)(x.size()))
#define ALL(x) x.begin(), x.end()

typedef int64_t ll;
typedef double dd;

template <template <class> class Alloc = allocator> struct DisjointSet {
  vector<int, Alloc<int>> tree;
  vector<ll, Alloc<ll>> sum;

  DisjointSet(int n) : tree(n, -1), sum(n, 0) {}

  bool Unite(int a, int b) {
    a = Find(a);
    b = Find(b);
    if (a == b)
      return false;
    if (-tree[b] > -tree[a])
      swap(a, b);
    // merge smaller tree (b) to (a)
    tree[a] += tree[b];
    sum[a] += sum[b];
    tree[b] = a;
    return true;
  }

  int GetSize(int a) {
    return -tree[Find(a)];
  }

  bool IsSameSet(int a, int b) {
    return Find(a) == Find(b);
  }

  int Find(int node) {
    return tree[node] < 0 ? node : (tree[node] = Find(tree[node]));
  }
};

void Solve(ll) {
  int n, m;
  if (not(cin >> n >> m))
    return;
  vector<int> need(n), recv(n);
  VEVE(i, 0, n) {
    cin >> need[i] >> recv[i];
    need[i] -= recv[i];
    need[i] = max(0, need[i]);
  }
  vector<vector<int>> adj(n);
  VEVE(e, 0, m) {
    int u, v;
    cin >> u >> v;
    --u, --v;
    adj[u].push_back(v);
    adj[v].push_back(u);
  }
  vector<bool> vis(n);
  priority_queue<tuple<int, int, int>> pq, bad;// -cost, from, to
  auto poss = [&](ll rem) {
    Arena<>::Clear();
    DisjointSet<Arena<>::Allocator> dsu(n);
    fill(ALL(vis), false);
    ll vis_cnt = 0;
    while (not pq.empty())
      pq.pop();
    VEVE(i, 0, n) {
      dsu.sum[i] = recv[i];
      if (need[i] <= rem) {
        vis[i] = true;
        ++vis_cnt;
        RARA(to, adj[i]) pq.emplace(-need[to], i, to);
      }
    }
    if (vis_cnt == n)
      return true;
    while (true) {
      bool changed = false;
      while (not bad.empty())
        bad.pop();
      while (not pq.empty()) {
        int cost, fr, to;
        tie(cost, fr, to) = pq.top();
        pq.pop();
        cost = -cost;
        if (dsu.IsSameSet(fr, to))
          continue;
        if (dsu.sum[dsu.Find(fr)] + rem >= cost) {
          dsu.Unite(fr, to);
          changed = true;
          if (not vis[to]) {
            vis[to] = true;
            if (++vis_cnt == n) {
              return true;
            }
            RARA(v, adj[to]) pq.emplace(-need[v], to, v);
          }
        } else {
          bad.emplace(-cost, fr, to);
        }
      }
      if (not changed)
        break;
      pq.swap(bad);
    }
    return false;
  };
  ll low = *min_element(ALL(need)), hig = *max_element(ALL(need));
  while (low <= hig) {
    const ll mid = (low + hig) / 2;
    if (poss(mid)) {
      hig = mid - 1;
    } else {
      low = mid + 1;
    }
  }
  const ll res = hig + 1 + accumulate(ALL(recv), ll(0));
  cout << res << endl;
}

void Init() {
  ios::sync_with_stdio(false);
  cin.tie(nullptr);
}

}

int32_t main() {
#ifdef AZN
  freopen("input.txt", "r", stdin);
#endif
  Init();
  ll tests = 11;
  VEVE(test, 1, tests + 1) Solve(test);
  return 0;
}

Measured memory_bytes: 16880