#include <bits/stdc++.h>

using namespace std;

namespace {

/**
 * Heavy light decomposition of tree with N vertices.
 * Vertices are numbered 0 to N - 1.
 * Maps each vertex to an integer in [0, N-1] such that a subtree is a consecutive interval
 * and a path is at most O(log N) disjoint consecutive intervals.
 *
 * Can also be used for lca queries in O(log N) time per query
 *
 * O(N) space
 *
 * @tparam Alloc allocator to use
 *
 * @test http://www.spoj.com/problems/GRASSPLA/
 *
 * @test TODO: Non-commutative queries and updates like SPOJ GSS7
 */
template <template <class> class Alloc = allocator> class HLDOT {
  vector<vector<int, Alloc<int>>, Alloc<vector<int, Alloc<int>>>> adj;
  vector<int, Alloc<int>> lef, rig, size, par, top, depth;
  int next_num;
  vector<pair<int, int>> segs;

  void DfsSize(int at) {
    size[at] = 1;
    for (auto& to : adj[at]) {
      if (to == par[at])
        swap(to, adj[at].back());
      if (to != par[at]) {
        par[to] = at;
        depth[to] = depth[at] + 1;
        DfsSize(to);
        size[at] += size[to];
        if (size[to] > size[adj[at].front()])
          swap(to, adj[at].front());
      }
    }
    if (not adj[at].empty() and adj[at].back() == par[at])
      adj[at].erase(--adj[at].end());
  }

  void DfsHld(int at) {
    lef[at] = next_num++;
    for (const auto& to : adj[at]) {
      top[to] = to == adj[at].front() ? top[at] : to;
      DfsHld(to);
    }
    rig[at] = next_num - 1;
  }

  void QueryUp(int v, int lca) {
    while (top[v] != top[lca]) {
      segs.emplace_back(lef[top[v]], lef[v]);
      v = par[top[v]];
    }
    if (v != lca)
      segs.emplace_back(lef[lca] + 1, lef[v]);
  }

 public:
  HLDOT(int vertices) : adj(vertices), lef(vertices), rig(vertices), size(vertices), par(vertices),
                        top(vertices), depth(vertices) {}

  void AddEdge(int a, int b) {
    adj[a].push_back(b);
    adj[b].push_back(a);
  }

  /**
   * @return Is a ancestor of b?
   */
  bool IsAncestor(int a, int b) const {
    return lef[a] <= lef[b] && rig[b] <= rig[a];
  }

  int GetLca(int a, int b) const {
    while (top[a] != top[b]) {
      if (depth[top[a]] < depth[top[b]])
        swap(a, b);
      a = par[top[a]];
    }
    return depth[a] < depth[b] ? a : b;
  }

  int GetDepth(int v) const { return depth[v]; }

  void Process(int root = 0) {
    next_num = 0;
    par[root] = top[root] = root;
    DfsSize(root);
    DfsHld(root);
  }

  pair<int, int> GetSubtreeSegment(int v) const {
    return {lef[v], rig[v]};
  }

  /**
   * @param include_lca: whether to include the lowest common ancestor in path or not
   * @return a list of segments of the given path. Segments are ordered from a to b so this works
   * for non-commutative path queries
   */
  const vector<pair<int, int>>& GetPathSegments(int a, int b, bool include_lca = true) {
    segs.clear();
    const int lca = GetLca(a, b);
    QueryUp(b, lca);
    if (include_lca)
      segs.emplace_back(lef[lca], lef[lca]);
    const auto count = segs.size();
    QueryUp(a, lca);
    reverse(segs.begin(), segs.end());
    reverse(segs.begin(), segs.end() - count);
    return segs;
  }

  int GetParent(int v) {
    return par[v];
  }
};


#ifdef AZN_LIB
#pragma once

#include "AbsoluteErrorReal.h"

#endif

#ifdef AZN_LIB
#pragma once
#endif

struct DefaultEpsilon {
  static constexpr double kEPS = 1e-5;
};

/**
 * A class to encapsulate using floating points with a fixed absolute precision kEPS.
 * Two reals x and y compare equal if |x-y| <= kEPS
 *
 * @tparam T floating point type to wrap
 * @tparam EpsilonWrapper a wrapper class that should contain the epsilon value ::kEPS
 *          as a static constexpr of type T
 */
template <class T = double, class EpsilonWrapper = DefaultEpsilon> class AbsoluteErrorReal {
  static_assert(is_floating_point<T>::value, "T must be floating point type");
  static constexpr T kEPS = EpsilonWrapper::kEPS;
  static_assert(isnormal(kEPS), "kEPS is not valid");

  T val_;

 public:

  AbsoluteErrorReal(const T& val = 0) : val_(val) {}

  explicit operator T() const { return val_; }

  // comparison operators
#define COMPARE_OP(op, ret) bool operator op(const AbsoluteErrorReal& o) const { return ret ;}

  COMPARE_OP(==, o.val_ - kEPS <= val_ and val_ <= o.val_ + kEPS)

  COMPARE_OP(!=, not(*this == o));

  COMPARE_OP(<, val_ < o.val_ - kEPS)

  COMPARE_OP(<=, *this < o or *this == o)

  COMPARE_OP(>, not(*this <= o))

  COMPARE_OP(>=, not(*this < o))

#undef COMPARE_OP

  AbsoluteErrorReal operator-() const { return {-val_}; }

  // standard non-mutative binary operators
#define CONST_BIN_OP(op) \
AbsoluteErrorReal operator op(const AbsoluteErrorReal& o) const { return {val_ op o.val_}; }

  CONST_BIN_OP(+)

  CONST_BIN_OP(-)

  CONST_BIN_OP(*)

  CONST_BIN_OP(/)

#undef CONST_BIN_OP

  // standard mutative binary operators
#define NON_CONST_BIN_OP(op) \
AbsoluteErrorReal& operator op(const AbsoluteErrorReal& o) { val_ op o.val_; return *this; }

  NON_CONST_BIN_OP(+=)

  NON_CONST_BIN_OP(-=)

  NON_CONST_BIN_OP(*=)

  NON_CONST_BIN_OP(/=)

#undef NON_CONST_BIN_OP

  friend ostream& operator<<(ostream& out, const AbsoluteErrorReal& o) { return out << o.val_; }
};


/**
 *
 *  @tparam T: floating point type
 *  @tparam MAX_EQUATIONS: max number of equations including objective function
 *  @tparam MAX_VARIABLES: max number of variables + 1 for constant
 *
 *  Two-phase simplex algorithm for solving linear programs of the form
 *
 *    maximize     c^T x
 *    subject to   Ax <= b
 *                 x >= 0
 *
 *    where
 *      m is the number of equations
 *      n is the number of variables
 *      A is an m x n matrix
 *      b is m x 1 vector
 *      c is n x 1 vector
 *
 *
 *    matrix_: (m+1) by (n+1) array:
 *    row 0     - objective fun coefficients (c)
 *    row 1:m   - <= inequalities (row (i-1) of A)
 *    matrix_[i][m] = b[i] for 1 <= i <= m
 *    matrix_[0][n] must be 0.
 *
 *    Usage:
 *      - call Init with the number of variables to zero matrix_ and set row count = 0
 *      - call SetObjectiveFunction(i,v) to set c[i] = v
 *      - call AddConstraint to get a pointer to a row of matrix_
 *          populate returned row with i-th row of A followed by b[i]
 *      - call Solve with pointer to T to output optimal value
 *      - Solve returns
 *         Result::kFinite if solution is finite, optimal value is put in value and x is put in solution_
 *         Result::kInfeasible if no solution exists
 *         Result::kUnbounded if LP has no finite solution
 *
 *    Complexity:
 *      Average case: between O(m*n) to O(m^2 n)
 *
 *  @test
 *      http://codeforces.com/gym/101190 problem D delight for a Cat
 */
template <class T = AbsoluteErrorReal<>, int MAX_EQUATIONS = 4000, int MAX_VARIABLES = 4000>
struct Simplex {
  T matrix_[MAX_EQUATIONS][MAX_VARIABLES], solution_[MAX_VARIABLES];
  int basis_[MAX_EQUATIONS], out_[MAX_VARIABLES];
  int rows_, cols_;

  enum class Result {
    kFinite,
    kInfeasible,
    kUnbounded,
  };

  void Init(int variables) {
    fill(matrix_[0], matrix_[0] + variables + 1, 0);
    rows_ = 0;
    cols_ = variables;
  }

  T* AddConstraint() {
    ++rows_;
    fill(matrix_[rows_], matrix_[rows_] + cols_ + 1, 0);
    return matrix_[rows_];
  }

  void SetObjectiveFunction(int var, T value) {
    matrix_[0][var] = value;
  }

  /**
   * returns whether (a1,a2) < (b1,b2) (lexicographic order)
   */
  bool ComparePair(const T& a1, const int& a2, const T& b1, const int& b2) const {
    if (a1 != b1)
      return a1 < b1;
    return a2 < b2;
  }

  void Pivot(int a, int b) {
    thread_local int non_zero_cols[MAX_VARIABLES];
    const T inv = static_cast<T>(1.0) / matrix_[a][b];
    int cnt = 0;
    for (int c = 0; c <= cols_; ++c)
      if (c != b and matrix_[a][c] != 0)
        non_zero_cols[cnt++] = c;
    for (int r = 0; r <= rows_; ++r) {
      if (r != a and matrix_[r][b] != 0) {
        const T val = matrix_[r][b] * inv;
        for (int i = 0; i < cnt; ++i)
          matrix_[r][non_zero_cols[i]] -= matrix_[a][non_zero_cols[i]] * val;
      }
    }
    for (int c = 0; c <= cols_; ++c)
      matrix_[a][c] *= inv;
    for (int r = 0; r <= rows_; ++r)
      matrix_[r][b] *= -inv;
    matrix_[a][b] = inv;
    swap(basis_[a], out_[b]);
  }

  /**
   * @param value: where optimal value of solution is written
   */
  Result Solve(T& value) {
    for (int c = 0; c <= cols_; ++c)
      matrix_[0][c] = -matrix_[0][c];
    for (int r = 0; r <= rows_; ++r)
      basis_[r] = -r;
    for (int c = 0; c <= cols_; ++c)
      out_[c] = c;
    while (true) {
      int rr = 1;
      for (int r = 1; r <= rows_; ++r)
        if (ComparePair(matrix_[r][cols_], basis_[r], matrix_[rr][cols_], basis_[rr]))
          rr = r;
      if (matrix_[rr][cols_] >= 0)
        break;
      int cc = 0;
      for (int c = 0; c < cols_; ++c)
        if (ComparePair(matrix_[rr][c], out_[c], matrix_[rr][cc], out_[cc]))
          cc = c;
      if (matrix_[rr][cc] >= 0)
        return Result::kInfeasible;
      Pivot(rr, cc);
    }
    while (true) {
      int cc = 0;
      for (int c = 0; c < cols_; ++c)
        if (ComparePair(matrix_[0][c], out_[c], matrix_[0][cc], out_[cc]))
          cc = c;
      if (matrix_[0][cc] >= 0)
        break;
      int rr = 0;
      for (int r = 1; r <= rows_; ++r)
        if (matrix_[r][cc] > 0 and
            (not rr or ComparePair(matrix_[r][cols_] * matrix_[rr][cc], basis_[r],
                                   matrix_[rr][cols_] * matrix_[r][cc], basis_[rr])))
          rr = r;
      if (matrix_[rr][cc] <= 0)
        return Result::kUnbounded;
      Pivot(rr, cc);
    }
    fill(solution_, solution_ + cols_, 0);
    for (int r = 1; r <= rows_; ++r)
      if (basis_[r] >= 0)
        solution_[basis_[r]] = matrix_[r][cols_];
    value = matrix_[0][cols_];
    return Result::kFinite;
  }
};


#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 int ll;
typedef double dd;
const ll MAX = 2002;
typedef Simplex<AbsoluteErrorReal<>, 5 * MAX, 3 * MAX> Simp;
Simp lp;

/**
 * M + 2*N vars
 * M + 4*N equations
 * first M vars are paths
 * next N vars are up edges
 * next N vars are down edges
 * p_j <= 1
 *
 * up_i = p_1 + ... + p_k  + (1-q_1 + ... + 1-q_l) > 0 <=>
 * up_i <= p_1 + ... + p_k + l - (q_1 + ... + q_l)
 * up_i <= 1
 *
 * dw_i = q_1 + ... + q_l + (1-p_1 + ... + 1-p_k) > 0 <=>
 * dw_i <= 1
 * dw_i <= k - (p_1 + ... + p_k) + q_1 + ... + q_l
 *
 */
void Solve(ll) {
  ll n, m;
  if (not(cin >> n >> m))
    return;
  HLDOT<> hld(n);
  VEVE(i, 0, n - 1) {
    ll a, b;
    cin >> a >> b;
    --a, --b;
    hld.AddEdge(a, b);
  }
  hld.Process();
  vector<pair<ll, ll>> paths(m);
  RARA(p, paths) {
    cin >> p.first >> p.second;
    --p.first;
    --p.second;
  }
  const ll vars = m + 2 * n;
  lp.Init(vars);
  vector<vector<ll> > up_paths(n), dw_paths(n);
  VEVE(j, 0, m) {
    const auto& p = paths[j];
    auto row = lp.AddConstraint();
    row[j] = row[vars] = 1;
    const auto lca = hld.GetLca(p.first, p.second);
    for (ll v = p.first; v != lca; v = hld.GetParent(v)) {
      up_paths[v].push_back(j);
    }
    for (ll u = p.second; u != lca; u = hld.GetParent(u)) {
      dw_paths[u].push_back(j);
    }
  }
  VEVE(i, 1, n) {
    const ll up = i + m, dw = i + m + n;
    lp.SetObjectiveFunction(up, 1);
    lp.SetObjectiveFunction(dw, 1);

    // up
    auto row = lp.AddConstraint();
    row[up] = row[vars] = 1;
    {
      row = lp.AddConstraint();
      row[up] = 1;
      RARA(j, up_paths[i]) {
        row[j] = 1;
      }
      RARA(j, dw_paths[i]) {
        row[j] = -1;
      }
      row[vars] = SIZE(up_paths[i]);
    }

    {
      // dw
      row = lp.AddConstraint();
      row[dw] = row[vars] = 1;
    }

    {
      row = lp.AddConstraint();
      row[dw] = 1;
      RARA(j, dw_paths[i]) {
        row[j] = 1;
      }
      RARA(j, up_paths[i]) {
        row[j] = -1;
      }
      row[vars] = SIZE(dw_paths[i]);
    }
  }
  AbsoluteErrorReal<> value;
  assert(lp.Solve(value) == Simp::Result::kFinite);
//  PlnArr(lp.solution_, lp.solution_ + vars);
  cout << (ll) ((dd) value) << "\n";
  VEVE(j, 0, m) {
    auto& p = paths[j];
    if (lp.solution_[j] > 0) swap(p.first, p.second);
    ++p.first;
    ++p.second;
  }
  RARA(p, paths) cout << p.first << " " << p.second << "\n";
}

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: 476928