#include <bits/stdc++.h>
using namespace std;
using ll = long long;
ll k;
vector<bool> dp(70000, false);
void fillDp(const unordered_map<bitset<16>, vector<ll>>& maskToNums) {
  dp[0] = true;
  for (auto m = 2; m <= (1 << (k + 1)) - 2; m = m + 2) {
    bitset<16> curr(m);
    if (maskToNums.find(curr) != maskToNums.end()) dp[m] = true;
    auto s = (m - 2) & m;
    while (s > 0) {
      bitset<16> diff(m - s);
      if (maskToNums.find(diff) != maskToNums.end() && dp[s] == true)
        dp[m] = true;
      s = (s - 2) & m;
    }
  }
}
vector<bitset<16>> findAnswer(
    const unordered_map<bitset<16>, vector<ll>>& maskToNums,
    const bitset<16>& subset) {
  if (maskToNums.find(subset) != maskToNums.end()) return {subset};
  auto m = subset.to_ulong();
  auto s = (m - 2) & m;
  while (s > 0) {
    bitset<16> diff(m - s);
    if (maskToNums.find(diff) != maskToNums.end() && dp[s] == true) {
      auto partAnswer = findAnswer(maskToNums, bitset<16>(s));
      partAnswer.emplace_back(diff);
      return partAnswer;
    }
    s = (s - 2) & m;
  }
  return {};
}
vector<bitset<16>> findCoverage(
    const unordered_map<bitset<16>, vector<ll>>& maskToNums,
    const bitset<16>& subset) {
  if (maskToNums.find(subset) != maskToNums.end()) return {subset};
  auto m = subset.to_ulong();
  auto s = (m - 2) & m;
  while (s > 0) {
    bitset<16> diff(m - s);
    if (maskToNums.find(diff) != maskToNums.end()) {
      auto cov = findCoverage(maskToNums, bitset<16>(s));
      if (!cov.empty()) {
        cov.emplace_back(diff);
        return cov;
      }
    }
    s = (s - 2) & m;
  }
  return {};
}
int main() {
  cin >> k;
  unordered_map<ll, ll> numToBox;
  unordered_map<ll, ll> boxToSum;
  for (auto i = 1; i <= k; ++i) {
    ll n;
    cin >> n;
    boxToSum.emplace(i, 0);
    for (auto j = 0; j < n; ++j) {
      ll a;
      cin >> a;
      numToBox.emplace(a, i);
      boxToSum.at(i) += a;
    }
  }
  ll sum = 0;
  for (const auto& item : boxToSum) sum += item.second;
  if (sum % k != 0)
    cout << "No";
  else {
    const auto aim = sum / k;
    unordered_map<ll, ll> numToNum;
    for (const auto& item : numToBox) {
      auto nextNum = item.first + aim - boxToSum.at(item.second);
      if (numToBox.find(nextNum) != numToBox.end())
        numToNum.emplace(item.first, nextNum);
    }
    const auto numToBoxCopy = numToBox;
    unordered_map<bitset<16>, vector<ll>> maskToNums;
    while (!numToBox.empty()) {
      auto start = numToBox.begin()->first;
      bitset<16> mask;
      mask[static_cast<size_t>(numToBox.begin()->second)] = true;
      vector<ll> vect;
      vect.emplace_back(start);
      auto nextIt = numToNum.find(start);
      while (nextIt != numToNum.end()) {
        auto next = nextIt->second;
        if (numToBox.find(next) == numToBox.end()) break;
        auto nextBox = numToBox.at(next);
        if (mask[static_cast<size_t>(nextBox)] == true) {
          if (next == start) {
            maskToNums.emplace(mask, vect);
            for (const auto& num : vect) numToBox.erase(num);
          }
          break;
        } else {
          mask[static_cast<size_t>(nextBox)] = true;
          vect.emplace_back(next);
          nextIt = numToNum.find(next);
        }
      }
      numToBox.erase(start);
    }
    fillDp(maskToNums);
    auto coverage = findAnswer(maskToNums, bitset<16>((1 << (k + 1)) - 2));
    if (!coverage.empty()) {
      cout << "Yes\n";
      map<ll, ll> boxToNumbers;
      map<ll, ll> numberToPutBox;
      for (const auto& cov : coverage) {
        auto nums = maskToNums.at(cov);
        for (const auto& num : nums) {
          auto nextNum = numToNum.at(num);
          auto box = numToBoxCopy.at(num);
          boxToNumbers.emplace(box, num);
          numberToPutBox.emplace(nextNum, box);
        }
      }
      for (const auto& boxToNumber : boxToNumbers)
        cout << boxToNumber.second << " "
             << numberToPutBox.at(boxToNumber.second) << "\n";
    } else {
      cout << "No";
    }
  }
}
