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: Accepted)

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

namespace ABC077C
{
    class Program
    {
        public 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);

            // b[i]を選んだときの組み合わせの数
            var bc = new long[n];
            for (int i = 0; i < n; i++)
            {
                bc[i] = n - Search.UpperBound(c, b[i]);
            }

            for (int i = n - 1 - 1; i >= 0; i--)
            {
                bc[i] += bc[i + 1];
            }

            long sum = 0;
            for (int i = 0; i < n; i++)
            {
                int j = Search.UpperBound(b, a[i]);
                if (j < n) sum += bc[j];
            }

            Console.WriteLine(sum);
        }

        /// <summary>
        /// 昇順ソート済みリストに対しての二分探索系
        /// </summary>
        class Search
        {
            /// <summary>
            /// Bound系の下請け
            /// </summary>
            static int FindBound<T>(IList<T> list, T v, int begin, int end, Func<T, T, bool> judge) where T : IComparable<T>
            {
                int low = begin, high = end;

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

                while (low < high)
                {
                    var mid = (high + low) / 2;

                    if (judge(v, list[mid])) { high = mid; }
                    else { low = mid + 1; }
                }

                return low;
            }

            /// <summary>
            /// v以上で最小のインデックスを返す
            /// </summary>
            public static int LowerBound<T>(IList<T> list, T v, int begin, int end) where T : IComparable<T>
                => FindBound(list, v, begin, end, (x, y) => x.CompareTo(y) <= 0);

            /// <summary>
            /// v以上で最小のインデックスを返す
            /// </summary>
            public static int LowerBound<T>(IList<T> list, T v) where T : IComparable<T>
                => LowerBound<T>(list, v, 0, list.Count());

            /// <summary>
            /// vより大きな最小のインデックスを返す
            /// </summary>
            public static int UpperBound<T>(IList<T> list, T v, int begin, int end) where T : IComparable<T>
                => FindBound(list, v, begin, end, (x, y) => x.CompareTo(y) < 0);

            /// <summary>
            /// vより大きな最小のインデックスを返す
            /// </summary>
            public static int UpperBound<T>(IList<T> list, T v) where T : IComparable<T>
                => UpperBound(list, v, 0, list.Count());


            /// <summary>
            /// 二分探索
            /// 見つからない場合は負数を返すが、bit反転するとLowerBoundと同じ値となる
            /// （標準のArray.BinarySearchとかと同じ仕様 ←つまり、普通はこれ使って）
            /// </summary>
            public static int Binary<T>(IList<T> list, T v) where T : IComparable<T>
            {
                var i = LowerBound(list, v);

                return (list[i].CompareTo(v) != 0) ? ~i : i;
            }


            /// <summary>
            /// v以下で最大のインデックスを返す
            /// O(logN+N)なので注意
            /// </summary>
            public static int OrLess<T>(IList<T> list, T v, int begin, int end) where T : IComparable<T>
            {
                var i = LowerBound(list, v, begin, end);

                if (list[i].CompareTo(v) != 0)
                {
                    // v未満で最大の値とする
                    if (i > 0) i--;
                }
                else
                {
                    // vと同じ値で、一番最後へ移動させる
                    while (i < end && list[i + 1].CompareTo(v) == 0) { i++; }
                }

                return i;
            }

            /// <summary>
            /// v以下で最大のインデックスを返す
            /// O(logN+N)なので注意
            /// </summary>
            public static int OrLess<T>(IList<T> list, T v) where T : IComparable<T>
                => OrLess(list, v, 0, list.Count());

            /// <summary>
            /// v未満で最大のインデックスを返す
            /// </summary>
            public static int LessThan<T>(IList<T> list, T v, int begin, int end) where T : IComparable<T>
            {
                var i = LowerBound(list, v, begin, end);

                // v未満で最大の値とする
                return (i > 0) ? i - 1 : i;
            }

            /// <summary>
            /// v以下で最大のインデックスを返す
            /// </summary>
            public static int LessThan<T>(IList<T> list, T v) where T : IComparable<T>
                => LessThan(list, v, 0, list.Count());
        }

        #region Main
        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("");

            Solve(input);

            Console.Out.Flush();
        }
        #endregion

        #region Competitive Template
#pragma warning disable CS0414
        static readonly int MOD = (int)1e9 + 7;
        static readonly int[] dx = { 1, 0, -1, 0 };
        static readonly int[] dy = { 0, 1, 0, -1 };
        /// <summary> 左上が原点 </summary>
        static readonly char[] dir = { 'R', 'U', 'L', 'D' };
#pragma warning restore CS0414

        /// <summary>
        /// 値を書き換え可能なTuple（ぐらいのイメージ）
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <typeparam name="U"></typeparam>
        public class Pair<T, U> : IComparable<Pair<T, U>>, IEquatable<Pair<T, U>>
            where T : IComparable<T>
            where U : IComparable<U>
        {
            public T V1 { get; set; }
            public U V2 { get; set; }

            public Pair(T v1, U v2)
            {
                this.V1 = v1;
                this.V2 = v2;
            }

            public Pair(Pair<T, U> src)
            {
                this.V1 = src.V1;
                this.V2 = src.V2;
            }

            public int CompareTo(Pair<T, U> other)
            {
                var r = this.V1.CompareTo(other.V1);
                if (r == 0) r = this.V2.CompareTo(other.V2);

                return r;
            }

            public bool Equals(Pair<T, U> other)
            {
                return other != null && this.CompareTo(other) == 0;
            }

            public override string ToString()
            {
                return $"{V1} {V2}";
            }

            public override bool Equals(object obj)
            {
                return Equals(obj as Pair<T, U>);
            }

            public override int GetHashCode()
            {
                var hashCode = 1989511945;
                hashCode = hashCode * -1521134295 + V1.GetHashCode();
                hashCode = hashCode * -1521134295 + V2.GetHashCode();
                return hashCode;
            }

            public static bool operator ==(Pair<T, U> a, Pair<T, U> b)
            {
                if (object.ReferenceEquals(a, null))
                {
                    return object.ReferenceEquals(b, null);
                }

                return a.Equals(b);
            }

            public static bool operator !=(Pair<T, U> a, Pair<T, U> b)
                => !(a == b);
            public static bool operator <(Pair<T, U> a, Pair<T, U> b)
                => a.CompareTo(b) < 0;
            public static bool operator <=(Pair<T, U> a, Pair<T, U> b)
                => a.CompareTo(b) <= 0;
            public static bool operator >(Pair<T, U> a, Pair<T, U> b)
                => a.CompareTo(b) > 0;
            public static bool operator >=(Pair<T, U> a, Pair<T, U> b)
                => a.CompareTo(b) >= 0;
        }

        public 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>
            /// 要素が存在するか
            /// </summary>
            /// <returns></returns>
            public bool Any()
                => q.Any() || read();


            /// <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);

            /// <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>
            void NextT<T>(T[] a, T[] b, T offset, Func<string, T> parse, Func<T, T, T> add)
            {
                if (a.Length != b.Length) return;
                if (!accumulate(a.Length * 2)) return;

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

            public void Next(string[] a, string[] b) => NextT(a, b, "", x => x, (x, y) => x);
            public void NextInt(int[] a, int[] b, int offset = 0) => NextT(a, b, offset, int.Parse, (x, y) => x + y);
            public void NextLong(long[] a, long[] b, long offset = 0) => NextT(a, b, offset, long.Parse, (x, y) => x + y);
            public void NextDouble(double[] a, double[] b, double offset = 0.0) => NextT(a, b, 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
    }
}