Problem:
Score : 300 points

Problem Statement

The season for Snuke Festival has come again this year. First of all, Ringo will perform a ritual to summon Snuke. For the ritual, he needs an altar, which consists of three parts, one in each of the three categories: upper, middle and lower.

He has N parts for each of the three categories. The size of the i-th upper part is A_i, the size of the i-th middle part is B_i, and the size of the i-th lower part is C_i.

To build an altar, the size of the middle part must be strictly greater than that of the upper part, and the size of the lower part must be strictly greater than that of the middle part. On the other hand, any three parts that satisfy these conditions can be combined to form an altar.

How many different altars can Ringo build? Here, two altars are considered different when at least one of the three parts used is different.

Constraints

1 \leq N \leq 10^5

1 \leq A_i \leq 10^9(1\leq i\leq N)

1 \leq B_i \leq 10^9(1\leq i\leq N)

1 \leq C_i \leq 10^9(1\leq i\leq N)

All input values are integers.

Input

Input is given from Standard Input in the following format:

N
A_1 ... A_N
B_1 ... B_N
C_1 ... C_N

Output

Print the number of different altars that Ringo can build.

Sample Input 1

2
1 5
2 4
3 6

Sample Output 1

3

The following three altars can be built:

Upper: 1-st part, Middle: 1-st part, Lower: 1-st part

Upper: 1-st part, Middle: 1-st part, Lower: 2-nd part

Upper: 1-st part, Middle: 2-nd part, Lower: 2-nd part

Sample Input 2

3
1 1 1
2 2 2
3 3 3

Sample Output 2

27

Sample Input 3

6
3 14 159 2 6 53
58 9 79 323 84 6
2643 383 2 79 50 288

Sample Output 3

87

Solution in C# (Mono 4.6.2.0) (judge verdict: Time Limit Exceeded)

using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;

namespace ARC084C
{
    class Program
    {
        static void Solve(Input input)
        {
            var n = input.NextInt();
            var a = input.NextInt(n);
            var b = input.NextInt(n);
            var c = input.NextInt(n);

            Array.Sort(a);
            Array.Sort(b);
            Array.Sort(c);

            Console.WriteLine(solvedp(a, b, c));
        }

        static long solvedp(int[] a, int[] b, int[] c)
        {
            // b[i]を選んだときの、組み合わせの数
            var bdp = new long[b.Length];
            for (int i = 0; i < b.Length; i++)
            {
                // b[i]より大きなc[ci]を探す
                var ci = FindBound.LowerBound(c, b[i]);
                if (ci >= c.Length) break;
                if (b[i] == c[ci])
                {
                    ci++;
                    if (ci >= c.Length) break;
                }

                bdp[i] = c.Length - ci;
            }

            // a[i]を選んだときの、組み合わせの数
            var adp = new long[a.Length];
            for (int i = 0; i < a.Length; i++)
            {
                var bi = FindBound.LowerBound(b, a[i]);
                if (bi >= b.Length) break;
                if (a[i] == b[bi])
                {
                    bi++;
                    if (bi >= c.Length) break;
                }

                adp[i] = bdp.Skip(bi).Sum();
            }

            return adp.Sum();
        }

        /// <summary>
        /// ソート済みリストに対して、ある値以上/以下となるindexを探す
        /// </summary>
        class FindBound
        {
            /// <summary>
            /// 以上、以下の下請け
            /// 二分探索
            /// </summary>
            /// <typeparam name="T"></typeparam>
            /// <param name="list"></param>
            /// <param name="v"></param>
            /// <param name="start"></param>
            /// <param name="end"></param>
            /// <param name="judge"></param>
            /// <returns></returns>
            static int findBound<T>(IList<T> list, T v, int start, int end, Func<int, int, bool> judge) where T : IComparable<T>
            {
                int low = start;
                int high = end;

                if (low > high)
                {
                    throw new ArgumentException();
                }

                while (low < high)
                {
                    var mid = ((high - low) / 2) + low;
                    if (judge(list[mid].CompareTo(v), 0))
                    {
                        low = mid + 1;
                    }
                    else
                    {
                        high = mid;
                    }
                }

                return low;
            }

            /// <summary>
            /// v以上の最初のインデックスを返す
            /// </summary>
            /// <typeparam name="T"></typeparam>
            /// <param name="list"></param>
            /// <param name="v"></param>
            /// <param name="start"></param>
            /// <param name="end"></param>
            /// <returns></returns>
            public static int LowerBound<T>(IList<T> list, T v, int start, int end) where T : IComparable<T>
            {
                return findBound<T>(list, v, start, end, (x, y) => x < y);
            }

            /// <summary>
            /// v以上の最初のインデックスを返す
            /// </summary>
            /// <typeparam name="T"></typeparam>
            /// <param name="list"></param>
            /// <param name="v"></param>
            /// <returns></returns>
            public static int LowerBound<T>(IList<T> list, T v) where T : IComparable<T>
            {
                return LowerBound<T>(list, v, 0, list.Count());
            }

            /// <summary>
            /// v以下の最初のインデックスを返す
            /// </summary>
            /// <typeparam name="T"></typeparam>
            /// <param name="list"></param>
            /// <param name="v"></param>
            /// <param name="start"></param>
            /// <param name="end"></param>
            /// <returns></returns>
            public static int UpperBound<T>(IList<T> list, T v, int start, int end) where T : IComparable<T>
            {
                return findBound<T>(list, v, start, end, (x, y) => x > y);
            }

            /// <summary>
            /// v以下の最初のインデックスを返す
            /// </summary>
            /// <typeparam name="T"></typeparam>
            /// <param name="list"></param>
            /// <param name="v"></param>
            /// <returns></returns>
            public static int UpperBound<T>(IList<T> list, T v) where T : IComparable<T>
            {
                return UpperBound<T>(list, v, 0, list.Count());
            }
        }


        #region Competitive Template
        static readonly int MOD = (int)1e9 + 7;

        public static void Main(string[] args)
        {
            // 出力が少ないときはこれをセットする方が時間かかるけれど
            // その場合は出力がボトルネックになっていないので、まあ良しとする
            var needsFlushOutput = true;
            if (needsFlushOutput)
            {
                // 細かく出力しないようにする
                var sw = new StreamWriter(Console.OpenStandardOutput()) { AutoFlush = false };
                Console.SetOut(sw);
            }

            // 通常は引数無しで、ファイルから読み出したい場合はpath指定する
            var input = new Input();
            // 仮想的に標準入力をセットする
            //input.SetText("2 10 20 30 40 60 80 ");

            Solve(input);

            Console.Out.Flush();
        }

        class Input
        {
            // 変な改行コードがたまに混じっているので、一応セパレート指定する
            // スペース単独指定の方がもちろん早い
            static readonly char[] separator = { ' ', '\r', '\n' };
            Queue<string> q { get; set; }
            StreamReader sr;

            /// <summary>
            /// 特定のファイルから読み出したい場合はpath指定する
            /// </summary>
            /// <param name="path"></param>
            public Input(string path = "")
            {
                q = new Queue<string>();

                if (string.IsNullOrEmpty(path))
                {
                    sr = new StreamReader(Console.OpenStandardInput());
                }
                else
                {
                    sr = new StreamReader(path);
                }
            }

            /// <summary>
            /// 入力予約
            /// </summary>
            /// <param name="items"></param>
            public void SetText(IEnumerable<string> items)
            {
                foreach (var item in items)
                {
                    SetText(item);
                }
            }

            /// <summary>
            /// 入力予約
            /// </summary>
            /// <param name="s"></param>
            /// <returns></returns>
            public bool SetText(string s)
            {
                if (s == null) return false;

                foreach (var elem in s.Trim().Split(separator, StringSplitOptions.RemoveEmptyEntries))
                {
                    q.Enqueue(elem);
                }

                return true;
            }

            /// <summary>
            /// 内部queueに入力からの値をsplitして格納する
            /// </summary>
            /// <returns></returns>
            bool read()
            {
                var s = sr.ReadLine();
                if (s == null) return false;

                foreach (var elem in s.Trim().Split(separator, StringSplitOptions.RemoveEmptyEntries))
                {
                    q.Enqueue(elem);
                }

                if (!q.Any()) return read();

                return true;
            }

            /// <summary>
            /// 次のstringを一つ読み込む
            /// </summary>
            /// <returns></returns>
            public string Next()
            {
                if (!q.Any())
                {
                    if (!read())
                    {
                        return "";
                    }
                }

                return q.Dequeue();
            }

            /// <summary>
            /// 指定個数queueにたまるまでenqueueし続ける
            /// </summary>
            /// <param name="n"></param>
            /// <returns></returns>
            bool accumulate(int n)
            {
                while (q.Count() < n)
                {
                    if (!read()) return false;
                }

                return true;
            }

            public int NextInt() => int.Parse(Next());
            public long NextLong() => long.Parse(Next());
            public double NextDouble() => double.Parse(Next());

            /// <summary>
            /// n個の要素をparseして、それぞれにoffsetをaddした配列を返す
            /// </summary>
            /// <typeparam name="T"></typeparam>
            /// <param name="n"></param>
            /// <param name="offset"></param>
            /// <param name="parse"></param>
            /// <param name="add"></param>
            /// <returns></returns>
            T[] NextT<T>(int n, T offset, Func<string, T> parse, Func<T, T, T> add)
            {
                if (!accumulate(n)) return null;

                var a = new T[n];

                for (int i = 0; i < n; i++)
                    a[i] = add(parse(q.Dequeue()), offset);

                return a;
            }

            public string[] Next(int n) => NextT(n, "", x => x, (x, y) => x);
            public int[] NextInt(int n, int offset = 0) => NextT(n, offset, int.Parse, (x, y) => x + y);
            public long[] NextLong(int n, long offset = 0) => NextT(n, offset, long.Parse, (x, y) => x + y);
            public double[] NextDouble(int n, double offset = 0.0) => NextT(n, offset, double.Parse, (x, y) => x + y);
        }

        static class Utils
        {
            public static void Print<T>(IEnumerable<T> ie, string sep = " ") => Console.WriteLine(string.Join(sep, ie));
            public static void Print<T>(params T[] objs) => Print(objs);
            public static T Max<T>(params T[] objs) => objs.Max();
            public static T Min<T>(params T[] objs) => objs.Min();

            public static void Swap<T>(ref T x, ref T y) where T : struct { var t = x; x = y; y = t; }
            public static void Swap<T>(IList<T> list, int i, int j) where T : struct { var t = list[i]; list[i] = list[j]; list[j] = t; }

            /// <summary>
            /// vでfillされたT[d1][d2]配列を作成する
            /// </summary>
            /// <typeparam name="T"></typeparam>
            /// <param name="d1"></param>
            /// <param name="d2"></param>
            /// <param name="v"></param>
            /// <returns></returns>
            public static T[][] Create2DArray<T>(int d1, int d2, T v = default(T))
            {
                return
                    Enumerable.Repeat(0, d1).Select(_ =>
                    Enumerable.Repeat(v, d2).ToArray()).ToArray();
            }

            /// <summary>
            /// vでfillされたT[d1][d2][d3]配列を作成する
            /// </summary>
            /// <typeparam name="T"></typeparam>
            /// <param name="d1"></param>
            /// <param name="d2"></param>
            /// <param name="d3"></param>
            /// <param name="v"></param>
            /// <returns></returns>
            public static T[][][] Create3DArray<T>(int d1, int d2, int d3, T v = default(T))
            {
                return
                    Enumerable.Repeat(0, d1).Select(_ =>
                    Enumerable.Repeat(0, d2).Select(__ =>
                     Enumerable.Repeat(v, d3).ToArray()).ToArray()).ToArray();
            }
        }
        #endregion
    }
}