#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using vi = vector<int>;
using vvi = vector<vi>;
using vll = vector<ll>;
using vvll = vector<vll>;
using vb = vector<bool>;
using vs = vector<string>;
mt19937 rng(chrono::steady_clock::now().time_since_epoch().count());
template <char MIN_CHAR, int SIGMA>
struct trie {
  struct node {
    array<int, SIGMA> link;
    int depth, suff, dict, wct, wid;
    node(int depth) : depth(depth), suff(0), dict(0), wct(0), wid(-1) {
      fill((link).begin(), (link).end(), 0);
    }
    int& operator[](char c) { return link[c - MIN_CHAR]; }
  };
  vs words;
  vector<node> nodes;
  trie(const vector<string>& words = {}) : words(words), nodes({node(0)}) {
    for (int w = 0; w < words.size(); w++) {
      int loc = 0;
      for (char c : words[w]) {
        assert(MIN_CHAR <= c && c < MIN_CHAR + SIGMA);
        if (!nodes[loc][c]) {
          nodes[loc][c] = nodes.size();
          nodes.push_back(nodes[loc].depth + 1);
        }
        loc = nodes[loc][c];
      }
      nodes[loc].dict = loc;
      nodes[loc].wct = 1;
      nodes[loc].wid = w;
    }
    for (queue<int> bfs({0}); !bfs.empty(); bfs.pop()) {
      int loc = bfs.front(), fail = nodes[loc].suff;
      if (!nodes[loc].dict) nodes[loc].dict = nodes[fail].dict;
      nodes[loc].wct += nodes[fail].wct;
      for (char c = MIN_CHAR; c < MIN_CHAR + SIGMA; c++) {
        int& succ = nodes[loc][c];
        if (succ) {
          nodes[succ].suff = loc ? nodes[fail][c] : 0;
          bfs.push(succ);
        } else
          succ = nodes[fail][c];
      }
    }
  }
  vi match(const string& text) const {
    vi res(words.size());
    priority_queue<pair<int, int>> found;
    int loc = 0;
    for (char c : text) {
      loc = nodes[loc].link[c - MIN_CHAR];
      int match = nodes[loc].dict;
      if (match) {
        if (!res[nodes[match].wid]++) found.push({nodes[match].depth, match});
      }
    }
    while (!found.empty()) {
      int loc = found.top().second;
      found.pop();
      int nxt = nodes[nodes[loc].suff].dict;
      if (nxt) {
        if (!res[nodes[nxt].wid]) found.push({nodes[nxt].depth, nxt});
        res[nodes[nxt].id] += res[nodes[loc].id];
      }
    }
    return res;
  }
  ll count(const string& text) const {
    ll res = 0;
    int loc = 0;
    for (char c : text) {
      loc = nodes[loc].link[c - MIN_CHAR];
      res += nodes[loc].wct;
    }
    return res;
  }
};
template <typename C, typename T, typename Q, typename V>
struct decomposable_search {
  int E;
  vector<vector<T>> elts;
  vector<C> collections;
  decomposable_search() : E(0) {}
  void insert(const T& elt) {
    if (__builtin_popcount(++E) == 1) {
      elts.push_back(vector<T>());
      elts.back().reserve(E);
      collections.push_back(C());
    }
    int put = 31 - __builtin_clz(E & -E);
    for (int i = 0; i < put; i++) {
      elts[put].insert(elts[put].end(), (elts[i]).begin(), (elts[i]).end());
      elts[i].clear();
      collections[i] = C();
    }
    elts[put].push_back(elt);
    collections[put] = C(elts[put]);
  }
  V search(Q query) const {
    V res(0);
    for (int i = 0; i < elts.size(); i++)
      if (!elts[i].empty()) {
        res += collections[i].count(query);
      }
    return res;
  }
};
int main() {
  ios_base::sync_with_stdio(false);
  cin.tie(0), cout.tie(0);
  decomposable_search<trie<'a', 26>, string, string, ll> add, del;
  int Q;
  cin >> Q;
  for (int q = 0, t; q < Q; q++) {
    string str;
    cin >> t >> str;
    if (t == 1)
      add.insert(str);
    else if (t == 2)
      del.insert(str);
    else
      cout << add.search(str) - del.search(str) << endl;
  }
  return 0;
}
