#include <bits/stdc++.h>
template <typename T>
class IntegerIterator
    : public std::iterator<std::input_iterator_tag, T, std::ptrdiff_t, T*, T> {
 public:
  explicit IntegerIterator(int value) : value(value) {}
  IntegerIterator& operator++() {
    ++value;
    return *this;
  }
  IntegerIterator operator++(int) {
    IntegerIterator copy = *this;
    ++value;
    return copy;
  }
  IntegerIterator& operator--() {
    --value;
    return *this;
  }
  IntegerIterator operator--(int) {
    IntegerIterator copy = *this;
    --value;
    return copy;
  }
  T operator*() const { return value; }
  bool operator==(IntegerIterator rhs) { return value == rhs.value; }
  bool operator!=(IntegerIterator rhs) { return !(*this == rhs); }

 private:
  T value;
};
template <typename T>
class IntegerRange {
 public:
  IntegerRange(T begin, T end) : begin_(begin), end_(end) { ; }
  IntegerIterator<T> begin() const { return IntegerIterator<T>(begin_); }
  IntegerIterator<T> end() const { return IntegerIterator<T>(end_); }

 private:
  T begin_;
  T end_;
};
template <typename T>
class ReversedIntegerRange {
 public:
  ReversedIntegerRange(T begin, T end) : begin_(begin), end_(end) { ; }
  std::reverse_iterator<IntegerIterator<T>> begin() const {
    return std::reverse_iterator<IntegerIterator<T>>(
        IntegerIterator<T>(begin_));
  }
  std::reverse_iterator<IntegerIterator<T>> end() const {
    return std::reverse_iterator<IntegerIterator<T>>(IntegerIterator<T>(end_));
  }

 private:
  T begin_;
  T end_;
};
template <typename T>
IntegerRange<T> range(T to) {
  return IntegerRange<T>(0, to);
}
template <typename T>
IntegerRange<T> range(T from, T to) {
  return IntegerRange<T>(from, to);
}
template <typename T>
IntegerRange<T> inclusiveRange(T to) {
  return IntegerRange<T>(0, to + 1);
}
template <typename T>
IntegerRange<T> inclusiveRange(T from, T to) {
  return IntegerRange<T>(from, to + 1);
}
template <typename T>
ReversedIntegerRange<T> downrange(T from) {
  return ReversedIntegerRange<T>(from, 0);
}
template <typename T>
ReversedIntegerRange<T> downrange(T from, T to) {
  return ReversedIntegerRange<T>(from, to);
}
template <typename T>
ReversedIntegerRange<T> inclusiveDownrange(T from) {
  return ReversedIntegerRange<T>(from + 1, 0);
}
template <typename T>
ReversedIntegerRange<T> inclusiveDownrange(T from, T to) {
  return ReversedIntegerRange<T>(from + 1, to);
}
template <typename R>
void sort(R& range) {
  std::sort(range.begin(), range.end());
}
template <typename R, typename Comp>
void sort(R& range, Comp comp) {
  std::sort(range.begin(), range.end(), comp);
}
template <typename R>
void reverse(R& range) {
  std::reverse(range.begin(), range.end());
}
template <typename R, typename T>
auto lower_bound(const R& range, const T& value) -> decltype(range.begin()) {
  return std::lower_bound(range.begin(), range.end(), value);
}
template <typename R, typename T, typename Comp>
auto lower_bound(const R& range, const T& value, Comp comp)
    -> decltype(range.begin()) {
  return std::lower_bound(range.begin(), range.end(), value, comp);
}
template <typename R, typename T>
auto upper_bound(const R& range, const T& value) -> decltype(range.begin()) {
  return std::upper_bound(range.begin(), range.end(), value);
}
template <typename R, typename T, typename Comp>
auto upper_bound(const R& range, const T& value, Comp comp)
    -> decltype(range.begin()) {
  return std::upper_bound(range.begin(), range.end(), value, comp);
}
template <typename R>
auto min_element(const R& range) -> decltype(range.begin()) {
  return std::min_element(range.begin(), range.end());
}
template <typename R>
auto max_element(const R& range) -> decltype(range.begin()) {
  return std::max_element(range.begin(), range.end());
}
template <typename R>
bool next_permutation(R& range) {
  return std::next_permutation(range.begin(), range.end());
}
template <typename T>
void unique(std::vector<T>& range) {
  range.erase(std::unique(range.begin(), range.end()), range.end());
}
template <typename R>
R sorted(R range) {
  sort(range);
  return range;
}
template <typename R, typename Comp>
R sorted(R range, Comp comp) {
  sort(range, comp);
  return range;
}
template <typename R>
R reversed(R range) {
  reverse(range);
  return range;
}
template <typename T>
class CoordinateMinimizer {
 public:
  CoordinateMinimizer() {}
  template <typename R>
  explicit CoordinateMinimizer(const R& range)
      : values(range.begin(), range.end()) {
    finalize();
  }
  void finalize() {
    sort(values);
    values.erase(std::unique(values.begin(), values.end()), values.end());
  }
  void add(const T& t) {
    ;
    values.push_back(t);
  }
  void add(T&& t) {
    ;
    values.push_back(std::move(t));
  }
  std::size_t find(const T& t) {
    ;
    auto iterator = lower_bound(values, t);
    ;
    return iterator - values.begin();
  }
  std::size_t size() {
    ;
    return values.size();
  };

 private:
  std::vector<T> values;
};
template <typename T>
class Range {
 public:
  Range(T begin, T end) : begin_(std::move(begin)), end_(std::move(end)) {}
  T begin() const { return begin_; }
  T end() const { return end_; }

 private:
  T begin_;
  T end_;
};
template <typename T>
Range<T> make_range(T begin, T end) {
  return Range<T>(begin, end);
}
template <typename T>
constexpr auto hasBegin(int) -> decltype(std::begin(std::declval<T>()), true) {
  return true;
}
template <typename T>
constexpr bool hasBegin(...) {
  return false;
}
template <typename T>
struct IsContainer : std::integral_constant<bool, hasBegin<T>(0)> {};
template <typename T, typename Merge>
class SegmentTreeBase {
 protected:
  SegmentTreeBase(std::size_t n, const T& defaultValue = T(),
                  const Merge& merge = Merge())
      : n(n),
        defaultValue(defaultValue),
        shift(calculateShift(n)),
        values(shift << 1, defaultValue),
        merge(merge) {}
  template <typename R, typename Enable = typename std::enable_if<
                            IsContainer<R>::value>::type>
  SegmentTreeBase(const R& range, const T& defaultValue = T(),
                  const Merge& merge = Merge())
      : SegmentTreeBase(std::distance(std::begin(range), std::end(range)),
                        defaultValue, merge) {
    std::copy(std::begin(range), std::end(range), values.begin() + shift);
    for (std::size_t index : downrange(shift, static_cast<std::size_t>(1))) {
      recalculate(index);
    }
  }
  static std::size_t calculateShift(std::size_t n) {
    std::size_t result = 1;
    while (result < n) {
      result <<= 1;
    }
    return result;
  }
  void recalculate(std::size_t index) {
    values[index] = merge(values[2 * index], values[2 * index + 1]);
  }
  std::size_t n;
  T defaultValue;
  std::size_t shift;
  std::vector<T> values;
  Merge merge;
};
template <typename T, typename Merge>
class BottomUpSegmentTree : protected SegmentTreeBase<T, Merge> {
 public:
  template <typename R>
  BottomUpSegmentTree(const R& range, const T& defaultValue = T(),
                      const Merge& merge = Merge())
      : SegmentTreeBase<T, Merge>(range, defaultValue, merge) {}
  const T& getElement(std::size_t index) {
    ;
    return values[index + shift];
  }
  T getResult(std::size_t l, std::size_t r) const {
    ;
    return internalGetResult(l + shift, r + shift);
  }
  template <typename Updater>
  void update(std::size_t index, const Updater& updater) {
    ;
    index += shift;
    updater(values[index]);
    for (std::size_t parent = index / 2; parent > 0; parent /= 2) {
      this->recalculate(parent);
    }
  }
  void set(std::size_t index, const T& value) {
    return update(index, [&value](T& element) { element = value; });
  }

 protected:
  using SegmentTreeBase<T, Merge>::defaultValue;
  using SegmentTreeBase<T, Merge>::merge;
  using SegmentTreeBase<T, Merge>::n;
  using SegmentTreeBase<T, Merge>::shift;
  using SegmentTreeBase<T, Merge>::values;

 private:
  T internalGetResult(std::size_t l, std::size_t r) const {
    if (l == r) {
      return defaultValue;
    }
    if (l & 1) {
      return merge(values[l], internalGetResult(l + 1, r));
    }
    if (r & 1) {
      return merge(internalGetResult(l, r - 1), values[r - 1]);
    }
    return internalGetResult(l / 2, r / 2);
  }
};
template <typename T>
class BottomUpSumSegmentTree : public BottomUpSegmentTree<T, std::plus<T>> {
 public:
  template <typename R>
  BottomUpSumSegmentTree(const R& range, const T& zero = T())
      : BottomUpSegmentTree<T, std::plus<T>>(range, zero) {}
};
using namespace std;
class TaskE {
 public:
  struct Query {
    int type, time, num;
    int id;
  };
  void solve(std::istream& in, std::ostream& out) {
    int n;
    in >> n;
    vector<Query> queries(n);
    vector<int> need_print(n);
    vector<int> ans(n);
    for (int i : range(n)) {
      in >> queries[i].type >> queries[i].time >> queries[i].num;
      queries[i].id = i;
      need_print[i] = queries[i].type == 3;
    }
    sort(queries, [](const Query& l, const Query& r) {
      return tie(l.num, l.id) < tie(r.num, r.id);
    });
    for (int i = 0; i < n;) {
      int j = i;
      while (queries[i].num == queries[j].num) {
        ++j;
      }
      CoordinateMinimizer<int> cm;
      for (int k = i; k < j; ++k) {
        cm.add(queries[k].time);
      }
      cm.finalize();
      BottomUpSumSegmentTree<int> tree(j - i);
      for (int k = i; k < j; ++k) {
        int time = cm.find(queries[k].time);
        if (queries[k].type == 3) {
          ans[queries[k].id] = tree.getResult(0, time);
        } else {
          int add = 1;
          if (queries[k].type == 2) {
            add = -1;
          }
          tree.set(time, add);
        }
      }
      i = j;
    }
    for (int i : range(n)) {
      if (need_print[i]) {
        out << ans[i] << '\n';
      }
    }
  }
};
int main() {
  std::ios_base::sync_with_stdio(false);
  TaskE solver;
  std::istream& in(std::cin);
  std::ostream& out(std::cout);
  in.tie(0);
  out << std::fixed;
  out.precision(20);
  solver.solve(in, out);
  return 0;
}
