using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Serialization.Formatters.Binary;
#if !DEBUG
    using System.IO;
#endif


public class Program {
    public static void Main() {
        #region SetAutoFlushIsFalse
#if !DEBUG
        var sw = new StreamWriter(Console.OpenStandardOutput()) { AutoFlush = false };
        Console.SetOut(sw);
#endif
        #endregion

        var n = ReadLib.ReadAsInt();
        var words = ReadLib
            .ReadNTimesAsCharArray(n)
            .Select(a => new string(a.OrderBy(c => c).ToArray()));

        var scoreDic = MyMethod
            .CountUp(words)
            .Where(kv => kv.Value >= 2);

        long score = 0;
        foreach (var item in scoreDic) {
            var s = item.Value;
            score += Math.BigMul(s, s - 1) / 2;
        }


        Console.WriteLine(score);
        #region Flush
#if !DEBUG
        Console.Out.Flush();
        sw.Close();
#endif
        #endregion
    }
}


static class Calculation {
}


/// <summary>UnionFind木</summary>
public class UnionFind {
    Dictionary<int, int> nodeToRoot;
    Dictionary<int, int> rank; // その根が配下に何個nodeがぶら下がっているか

    public UnionFind(int n, int startAt = 0) {
        nodeToRoot = Enumerable.Range(startAt, n).ToDictionary(i => i, i => i);    // n個根を作る
        rank = Enumerable.Range(startAt, n).ToDictionary(i => i, i => 0);
    }

    /// <summary>根を得る</summary>
    public int GetRoot(int x) {
        if (nodeToRoot[x] == x) { // 渡されたノードが根
            return x;
        }
        nodeToRoot[x] = GetRoot(nodeToRoot[x]);  // parentの根を得てset。調べるたびに最適化。

        return nodeToRoot[x];
    }

    /// <summary>xとyの木を併合 => 根を同一にする</summary>
   　public void Unite(int x, int y) {
        var rootX = GetRoot(x);
        var rootY = GetRoot(y);

        if (rootX == rootY) {
            return; // pass
        }

        if (rank[rootX] < rank[rootY]) {    // yをxへ併合
            nodeToRoot[rootX] = nodeToRoot[rootY];
        } else {
            nodeToRoot[rootX] = nodeToRoot[rootY];

            if (rank[rootX] == rank[rootY]) {
                rank[rootX]++;  // yの下に配下が1つ追加される。
            }
        }
    }

    public bool AreSameRoot(int x, int y) {
        return GetRoot(x) == GetRoot(y);
    }
}

/// <summary>根付き木</summary>
public class RootedTree {
    public int Id { get; }
    public int Parent { get; }
    public int Depth { get; set; }
    int[] Childs { get; }

    public RootedTree(int id, int parent, int[] childs) {
        this.Id = id;
        this.Parent = parent;
        this.Childs = childs;
    }
}



static class Quick {

    internal static void Calculate(int[] ts) {
        QuickSort(ts, 0, ts.Count() - 1);
    }

    private static void QuickSort(int[] ts, int p, int r) {
        if (p < r) {
            var q = Partition(ts, p, r);
            QuickSort(ts, p, q - 1);
            QuickSort(ts, q + 1, r);
        }
    }

    public static int Partition(int[] sArr, int p, int r) {
        var x = sArr[r];
        var i = p - 1;
        for (int j = p; j < r; j++) {
            if (sArr[j] <= x) {
                i++;
                sArr.Swap(i, j);
            }
        }
        sArr.Swap(i + 1, r);
        return i + 1;
    }

}

static class ReadLib {
    public static long ReadAsLong() {
        return long.Parse(Console.ReadLine());
    }

    public static int ReadAsInt() {
        return int.Parse(Console.ReadLine());
    }

    public static int[] ReadAsIntArray(int step = 0) {
        return Console.ReadLine().Split(' ').Select(s => int.Parse(s) + step).ToArray();
    }

    public static long[] ReadAsLongArray(int step = 0) {
        return Console.ReadLine().Split(' ').Select(s => long.Parse(s) + step).ToArray();
    }

    public static ulong[] ReadAsULongArray() {
        return Console.ReadLine().Split(' ').Select(s => ulong.Parse(s)).ToArray();
    }

    public static string[] ReadAsStringArray() {
        return Console.ReadLine().Split(' ');
    }

    public static int[] ReadNTimesAsInt(int n, int step = 0) {
        return Enumerable.Range(1, n).Select(s => int.Parse(Console.ReadLine()) + step).ToArray();
    }

    public static char[][] ReadNTimesAsCharArray(int n) {
        return Enumerable.Range(1, n).Select(i => Console.ReadLine().ToArray()).ToArray();
    }

    public static long[] ReadNTimesAsLong(int n, int step = 0) {
        return Enumerable.Range(1, n).Select(s => long.Parse(Console.ReadLine()) + step).ToArray();
    }

    public static int[][] ReadNTimesAsIntArray(int n, int step = 0) {
        return Enumerable.Range(1, n).Select(s => ReadAsIntArray(step)).ToArray();
    }

    public static string[] ReadNTimesAsString(int n) {
        return Enumerable.Range(1, n).Select(s => Console.ReadLine()).ToArray();
    }

}

static class WriteLib {
    public static void Write<T>(IEnumerable<T> ts) {
        ts.ToList().ForEach(s => Console.WriteLine(s));
    }
}

static class MyMath {
    /// <summary>階乗</summary>
    public static long Factorial(int n) {
        if (n == 0) {
            return 1;
        }

        return Permutation(n, 0);
    }

    public static long Permutation(int n, int k) {
        return Enumerable.Range(0, n - k).Select(i => n - i).Aggregate(1, (b, i) => b * i);
    }

    #region FullPower
    /// <summary>総乗、π</summary>
    public static long FullPower(IEnumerable<long> vs) {
        return vs.Aggregate(1L, (a, b) => a * b);
    }

    public static long FullPower(IEnumerable<int> vs) {
        return vs.Aggregate(1L, (a, b) => a * b);
    }
    #endregion

    /// <summary>最大公約数</summary>
    public static long Gcd(long item1, long item2) {
        var a = Math.Max(item1, item2);
        var b = Math.Min(item1, item2);

        while (true) {
            long mod;
            Math.DivRem(a, b, out mod);
            if (mod == 0) {
                break;
            }

            a = b;
            b = mod;
        }
        return b;
    }

    /// <summary>約分。最大公約数で割ったものを返す。</summary>
    public static Tuple<long, long> DeviedByGcd(long item1, long item2) {
        var gcd = Gcd(item1, item2);
        return new Tuple<long, long>(item1 / gcd, item2 / gcd);
    }

    public static long Lcm(int item1, int item2) {
        return Math.BigMul(item1, item2) / Gcd(item1, item2);
    }

    public static ModCombination ModCombination(int maxN, int mod = 1000000007) {
        return new ModCombination(maxN, mod);
    }

    /// <summary>a^n mod</summary>
    public static int ModPow(long a, long n, int mod = 1000000007) {
        long res = 1;
        long pow = a;

        while (n > 0) {
            if ((n & 1) == 1) {
                res = (pow * res) % mod;    // resにaをかける
            }

            pow = (pow * pow) % mod;    // 次のaはa^2
            n >>= 1;
        }

        return (int)res;
    }
}

static class MyMethod {
    /// <summary>数え上げて、key, 出現回数に変換</summary>
    public static Dictionary<T, int> CountUp<T>(IEnumerable<T> str) {
        return
            str
            .GroupBy(c => c)
            .Select(g => new KeyValuePair<T, int>(g.Key, g.Count()))
            .ToDictionary(e => e.Key, e => e.Value);
    }

    /// <summary>計数ソート</summary>
    public static int[] CountingSort(int[] aArr, int k) {
        int[] b, c;
        var n = aArr.Length;
        b = new int[n];
        c = Enumerable.Repeat(0, k + 1).ToArray();


        for (int i = 0; i < n; i++) {
            c[aArr[i]]++;
        }

        for (int i = 1; i <= k; i++) {
            c[i] += c[i - 1];
        }

        for (int i = n - 1; i >= 0; i--) {
            b[c[aArr[i]] - 1] = aArr[i];
            c[aArr[i]]--;
        }

        return b;
    }
}

/// <summary>無向グラフ</summary>
class UndirectedGraph {
    public Dictionary<int, List<int>> Graph { get; }

    /// <param name="graphArr">point1 to point2</param>
    /// <param name="n">グラフの全要素数</param>
    public UndirectedGraph(int[][] graphArr, int n) {
        Graph = new Dictionary<int, List<int>>();

        for (int i = 1; i <= n; i++) {
            Graph.Add(i, new List<int>());
        }

        foreach (var arr in graphArr) {
            Graph[arr[0]].Add(arr[1]);
            Graph[arr[1]].Add(arr[0]);
        }
    }
}
/// <summary>DeepCopyするクラス</summary>
class DeepCopier {
    BinaryFormatter b = new BinaryFormatter();
    public T DeepCopy<T>(T obj) {
        T result;
        using (var mem = new MemoryStream()) {
            b.Serialize(mem, obj); //  binaryFormatterによりobjをmemにserial化して書き出す
            mem.Position = 0; // memのpotisionを先頭に戻す
            result = (T)b.Deserialize(mem); // memの内容を読み取る。
        }
        return result;
    }
}

static class MergeSort {
    public static long[] Sort(long[] arr) {
        return Sort(arr, 0, arr.Count());
    }

    // rightは+1されていることに注意
    private static long[] Merge(long[] sArr, int left, int mid, int right) {
        var n1 = mid - left;
        var n2 = right - mid;

        var lArr = new long[n1 + 1];
        var rArr = new long[n2 + 1];

        for (int i = 0; i < n1; i++) {
            lArr[i] = sArr[left + i];
        }
        lArr[n1] = int.MaxValue;

        for (int i = 0; i < n2; i++) {
            rArr[i] = sArr[mid + i];
        }
        rArr[n2] = int.MaxValue;

        int l = 0, r = 0;
        for (int k = left; k < right; k++) {
            if (lArr[l] <= rArr[r]) {
                sArr[k] = lArr[l];
                l++;
            } else {
                sArr[k] = rArr[r];
                r++;
            }
        }
        return sArr; // 要素数はright - left
    }

    private static long[] Sort(long[] sArr, int left, int right) {
        if (left + 1 < right) {
            var mid = (left + right) / 2;
            sArr = Sort(sArr, left, mid);
            sArr = Sort(sArr, mid, right);
            sArr = Merge(sArr, left, mid, right);
        }
        return sArr;
    }
}

/// <summary>あまりを計算するためのintのラッパー</summary>
struct ModInt {
    int Value { get; }
    int Mod { get; }

    public override string ToString() {
        return Value.ToString();
    }

    public static explicit operator ModInt(long value) {
        return new ModInt(value);
    }

    public ModInt(long value, int mod = 1000000007) {
        this.Value = (int)(value % mod);
        this.Mod = mod;
    }

    public static explicit operator int(ModInt modInt) {
        return modInt.Value;
    }

    private static void CheckNotEqualMod(ModInt item1, ModInt item2) {
        if (item1.Mod != item2.Mod) {
            throw new Exception("mod not equal");
        }
    }

    public static ModInt operator +(ModInt item1, ModInt item2) {
        CheckNotEqualMod(item1, item2);
        long value = item1.Value + item2.Value;
        return new ModInt(value, item1.Mod);
    }

    public static ModInt operator *(ModInt item1, ModInt item2) {
        CheckNotEqualMod(item1, item2);
        long value = Math.BigMul(item1.Value, item2.Value);
        return new ModInt(value, item1.Mod);
    }

    public static ModInt operator -(ModInt item1, ModInt item2) {
        CheckNotEqualMod(item1, item2);
        long value = item1.Value - item2.Value;
        if (value < 0) {
            value += item1.Mod;
        }
        return new ModInt(value, item1.Mod);
    }

    public static ModInt operator +(ModInt item1, int item2) {
        return item1 + new ModInt(item2, item1.Mod);
    }

    public static ModInt operator *(ModInt item1, int item2) {
        return item1 * new ModInt(item2, item1.Mod);
    }

    public static ModInt operator -(ModInt item1, int item2) {
        return item1 - new ModInt(item2, item1.Mod);
    }


    public override int GetHashCode() {
        return this.Value;
    }

    public override bool Equals(object obj) {
        if (obj == null || this.GetType() != obj.GetType()) {
            return false;
        }

        var modIntObj = (ModInt)obj;
        return modIntObj.Value == this.Value;
    }

    public static bool operator ==(ModInt item1, ModInt item2) {
        return item1.Value == item2.Value;
    }

    public static bool operator !=(ModInt item1, ModInt item2) {
        return !(item1 == item2);
    }

    public static bool operator ==(ModInt item1, int item2) {
        return item1.Value == item2;
    }

    public static bool operator !=(ModInt item1, int item2) {
        return !(item1.Value == item2);
    }

    public static bool operator >=(ModInt item1, ModInt item2) {
        return item1.Value >= item2.Value;
    }

    public static bool operator <=(ModInt item1, ModInt item2) {
        return item1.Value <= item2.Value;
    }

    public static bool operator >=(ModInt item1, int item2) {
        return item1.Value >= item2;
    }

    public static bool operator <=(ModInt item1, int item2) {
        return item1.Value <= item2;
    }

    public static bool operator >(ModInt item1, ModInt item2) {
        return item1.Value > item2.Value;
    }

    public static bool operator <(ModInt item1, ModInt item2) {
        return item1.Value < item2.Value;
    }

    public static bool operator >(ModInt item1, int item2) {
        return item1.Value >= item2;
    }

    public static bool operator <(ModInt item1, int item2) {
        return item1.Value <= item2;
    }
}

/// <summary>組み合わせ</summary>
class ModCombination {
    private int mod;
    private int[] fac;
    private int[] finv;
    private int[] inv;

    public ModCombination(int max, int mod = 1000000007) {
        this.mod = mod;
        fac = new int[max]; // modが法のn!の列

        inv = new int[max]; // indexの逆元の列
        finv = new int[max]; // modを法とした、invの累積の累積

        fac[0] = 1; fac[1] = 1;
        finv[0] = 1; finv[1] = 1;
        inv[1] = 1;

        for (int i = 2; i < max; i++) {
            fac[i] = (int)(Math.BigMul(fac[i - 1], i) % mod);  // factorを求める
            var s = Math.BigMul(inv[mod % i], mod / i) % mod;
            inv[i] = (int)(mod - s);　 // iに対する逆元を求める
            finv[i] = (int)(Math.BigMul(finv[i - 1], inv[i]) % mod);
        }
    }
    /// <summary>nCk</summary>
    public int Calculation(int n, int k) {
        if (n < k) return 0;
        if (n < 0 || k < 0) return 0;

        return (int)(Math.BigMul(fac[n], (int)(Math.BigMul(finv[k], finv[n - k]) % mod)) % mod);
    }
}

/// <summary>拡張メソッドs</summary>
static class Extention {
    /// <summary>iとjを入れ替える</summary>
    public static void Swap<T>(this T[] arr, int i, int j) {
        var buf = arr[i];
        arr[i] = arr[j];
        arr[j] = buf;
    }

    /// <summary>
    /// partition。末尾sArr[r]を基準として、左に基準より小さい数、右に基準より大きい数が存在するように配列を並び替える。
    /// </summary>
    /// <param name="sArr"></param>
    /// <param name="p">partitionの先頭の添字</param>
    /// <param name="r">partitionの末尾の添字</param>
    /// <returns>partitionの基準が存在する添字</returns>
    public static int Partition(this int[] sArr, int p, int r) {
        var x = sArr[r];
        var i = p - 1;
        for (int j = p; j < r; j++) {
            if (sArr[j] <= x) {
                i++;
                sArr.Swap(i, j);
            }
        }
        sArr.Swap(i + 1, r);
        return i + 1;
    }

    /// <summary>深いコピーをする</summary>
    public static T DeepCopy<T>(this T obj) {
        T result;
        var b = new BinaryFormatter();
        using (var mem = new MemoryStream()) {
            b.Serialize(mem, obj); //  binaryFormatterによりobjをmemにserial化して書き出す
            mem.Position = 0; // memのpotisionを先頭に戻す
            result = (T)b.Deserialize(mem); // memの内容を読み取る。
        }
        return result;
    }

    public static string Left(this string str, int length) {
        return str.Substring(0, length);
    }

    public static string Right(this string str, int length) {
        return str.Substring(str.Length - length, length);
    }

    public static int IndexOf<T>(this T[] array, T obj) {
        return Array.IndexOf(array, obj);
    }
}

Measured memory_bytes: 46336