#include <bits/stdc++.h>
using namespace std;

#define REP(i, n) for (int i = 0; i < n; i++) 
#define INF 1000000000 

typedef vector<int> vi;
typedef vector<vector<int>> vvi;
typedef vector<string> vs;
typedef long double ld;

//ある値がvector内の要素に含まれているか否か判定する
int vector_finder(std::vector<int> vec, int number) {
  auto itr = std::find(vec.begin(), vec.end(), number);
  size_t index = std::distance(vec.begin(), itr);
  if (index != vec.size()) { // 発見できたとき
    return 1;
  } else { // 発見できなかったとき
    return 0;
  }
}

struct edge_t {
  int dest;
  int weight;
};

struct vertex_t {
  vector<edge_t> edges;
  vector<int> weight;

  // for dijkstra
  bool done; // confirm or not
  int cost;  // now cost
  int from;

  bool operator<(const vertex_t &another) const { // priority --> min
    return another.cost < cost;                   // order(cost)
  }

  bool operator>(const vertex_t &another) const { // priority --> min
    return another.cost > cost;                   // order(cost)
  }

  bool operator==(const vertex_t &another) const {
    return another.cost == cost; // order(cost)
  }

  vertex_t() {
    cost = INF;
    done = false;
  }
};

int bellman_ford(vector<vertex_t> &graph, int size, int start) {
  int i, j, k, count = 0;
  bool flag = false;
  graph[start].cost = 0;
  REP(i, size) {
    REP(j, size) {
      vertex_t cur = graph[j];
      for (edge_t edge : cur.edges) {
        if (cur.cost + edge.weight < graph[edge.dest].cost) {
          if (i == size - 1)
            flag = true;
          graph[edge.dest].cost = cur.cost + edge.weight;
        }
      }
    }
  }
  if (flag) {
    return 1;
  } else {
    return 0;
  }
}

class tree_t {
  public:
  bool is_leaf;
  tree_t *right;
  tree_t *left;

  tree_t() {
    is_leaf = false;
    right = NULL;
    left = NULL;
  }
};

vector<string> splitstr(const string &s, char delim) {
  vector<string> elems;
  string item;
  for (char ch : s) {
    if (ch == delim) {
      if (!item.empty())
        elems.push_back(item);
      item.clear();
    } else {
      item += ch;
    }
  }
  if (!item.empty())
    elems.push_back(item);
  return elems;
}

unsigned getDigitNum(unsigned num) {
  unsigned digit = 0;
  while (num != 0) {
    num /= 10;
    digit++;
  }
  return digit;
}

////////////////////////// let's code! /////////////////////////////

// REP(i, N) -> i = (0..N-1)
// for(int i=0; i < N; i++) -> 最終的にiはN

///// memo /////

int i, j, k;

///// variables frequent to use /////



long permutation(int n, int min) {
  if (n == min) {
    return n;
  } else {
    return n * permutation(n - 1, min);
  }
}

long combination(int n, int r) {
  if(n < r) return 0;
  else return (long) (permutation(n, n-r+1) / permutation(r, 1));
}

int main() {
  int N;
  cin >> N;
  vector<string> S(N);

  REP(i, N) cin >> S[i];

  REP(i, N) {
    REP(j, 10) {
      REP(k, 10) {
        if (S[i][j] < S[i][k]) {
          char tmp = S[i][j];
          S[i][j] = S[i][k];
          S[i][k] = tmp;
        }
      }
    }
  }

  sort(S.begin(), S.end());

  long ans = 0;
  
  vector<pair<string, int>> runlength;
  string s;
  int count;
  REP(i, N) {
    if (i == 0) {
      count=1, s = S[i];
    } else if (s == S[i]) {
      count++;
    } else {
      runlength.push_back(make_pair(s, count));
      s = S[i];
      count = 1;
    }
    if(i==N-1) runlength.push_back(make_pair(s, count));
  }

  REP(i, runlength.size()) ans += combination(runlength[i].second, 2);

  cout << ans << endl;
}


Measured memory_bytes: 9464