#include <cstddef>
#include <exception>
#include <fstream>
#include <iostream>
#include <limits>
#include <string>
#include <string_view>
#include <unordered_map>
#include <unordered_set>

class TripletWordAdapter
{
public:
  class TripletWordIterator
  {
  public:
    TripletWordIterator(const std::string_view word, const std::size_t position)
      : m_word(word)
      , m_position(position)
    {
      if (m_word.length() <= 4)
        throw std::logic_error("The word is too short to iterate through");
    }

    std::pair<std::string_view, std::string_view> operator*() const
    {
      if (m_position + 4 > m_word.length())
        throw std::logic_error("Iterator is out of a valid range");

      return std::pair(m_word.substr(m_position, 3),
                       m_word.substr(m_position + 1, 3));
    }

    TripletWordIterator& operator++()
    {
      ++m_position;
      if (m_position > m_word.length() - 3)
        throw std::logic_error("Iterator is out of a valid range");

      return *this;
    }

    bool operator!=(const TripletWordIterator other) const
    {
      return m_position != other.m_position;
    }

  private:
    const std::string_view m_word;
    std::size_t m_position;
  };

  TripletWordAdapter(std::string_view word)
    : m_word(word)
  {
    if (m_word.length() < 4)
      throw std::logic_error("Can't wrap a word too short to iterate through");
  }

  TripletWordIterator begin() const { return TripletWordIterator(m_word, 0); }
  TripletWordIterator end() const
  {
    return TripletWordIterator(m_word, m_word.length() - 3);
  }

private:
  const std::string_view m_word;
};

class WordGraphSolver
{
public:
  void ProcessWord(const std::string_view word)
  {
    for (auto [from, to] : TripletWordAdapter(word)) {
      if (from.compare(to) < 0)
        std::swap(from, to);

      if (++m_edges[std::string(from) + ' ' + std::string(to)] != 1)
        continue;

      // A new edge have appeared, meaning a new vertex has been added to a
      // graph
      m_vertices.emplace(from);
      m_vertices.emplace(to);
    }
  }

  void PrintResults() const
  {
    std::cout << m_vertices.size() << std::endl;
    std::cout << m_edges.size() << std::endl;
    for (const auto& [edge, weight] : m_edges)
      std::cout << edge << ' ' << weight << std::endl;
  }

private:
  std::unordered_set<std::string> m_vertices;
  std::unordered_map<std::string, std::uint32_t> m_edges;
};

int
main()
{
  std::ifstream input("input.txt");
  input.ignore(std::numeric_limits<std::streamsize>::max(), '\n');

  WordGraphSolver solver;
  std::string line;
  while (std::getline(input, line))
    solver.ProcessWord(line);

  solver.PrintResults();
}