Problem:
Score : 400 points

Problem Statement

Given is a positive integer N. Consider repeatedly applying the operation below on N:

First, choose a positive integer z satisfying all of the conditions below:

z can be represented as z=p^e, where p is a prime number and e is a positive integer;

z divides N;

z is different from all integers chosen in previous operations.

Then, replace N with N/z.

Find the maximum number of times the operation can be applied.

Constraints

All values in input are integers.

1 \leq N \leq 10^{12}

Input

Input is given from Standard Input in the following format:

N

Output

Print the maximum number of times the operation can be applied.

Sample Input 1

24

Sample Output 1

3

We can apply the operation three times by, for example, making the following choices:

Choose z=2 (=2^1). (Now we have N=12.)

Choose z=3 (=3^1). (Now we have N=4.)

Choose z=4 (=2^2). (Now we have N=1.)

Sample Input 2

1

Sample Output 2

0

We cannot apply the operation at all.

Sample Input 3

64

Sample Output 3

3

We can apply the operation three times by, for example, making the following choices:

Choose z=2 (=2^1). (Now we have N=32.)

Choose z=4 (=2^2). (Now we have N=8.)

Choose z=8 (=2^3). (Now we have N=1.)

Sample Input 4

1000000007

Sample Output 4

1

We can apply the operation once by, for example, making the following choice:

z=1000000007 (=1000000007^1). (Now we have N=1.)

Sample Input 5

997764507000

Sample Output 5

7

Solution in C# (.NET Core 3.1.201) (judge verdict: Accepted)

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.Tracing;
using System.IO.Enumeration;
using System.Linq;
using System.Net.NetworkInformation;
using System.Runtime.CompilerServices;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using Extensions;
using DSA;
using static System.Math;
using static Extensions.MathExtension;
using static Extensions.ConsoleInputExtension;
using static Extensions.ConsoleOutputExtension;

class Solver
{
    public void Solve()
    {
        //Solve Code Here
        // long i = 1_000_00
        long n = Cin;
        int ans = 0;
        foreach (var (_, count) in PrimeFactorization(n))
        {
            ans += (int) ((Sqrt(1 + count * 8) - 1) / 2);
        }

        Coutln(ans);
    }
}

//Other Classes Here
class Node
{
    public int Index { get; set; }
    public List<Node> Connections { get; } = new List<Node>();

    public void AddConnection(Node n)
    {
        this.Connections.Add(n);
        n.Connections.Add(this);
    }
}

#if !DEBUG
class EntryPoint
{
    static void Main(string[] args)
    {
        new Solver().Solve();
        Flush();
    }
}
#endif


namespace DSA
{
    /// <summary>
    /// 二分ヒープによる優先度付きキュー
    /// </summary>
    /// <typeparam name="T">内部に格納するデータ型</typeparam>
    public class PriorityQueue<T>
    {
        private readonly List<Tuple<int, T>> _list = new List<Tuple<int, T>>();

        public int Count => _list.Count;

        public PriorityQueue()
        {
        }

        /// <summary>
        /// 要素をひとつ入力する
        /// O(logN)
        /// </summary>
        /// <param name="item">要素</param>
        /// <param name="priority">優先度</param>
        public void Push(T item, int priority)
        {
            _list.Add(Tuple.Create(priority, item));

            int itemIndex = Count - 1, parentIndex;
            while ((parentIndex = GetParentIndex(itemIndex)) != -1 &&
                   priority                                  > _list[parentIndex].Item1)
            {
                Swap(itemIndex, parentIndex);
                itemIndex = parentIndex;
            }
        }

        private int GetParentIndex(int index)
        {
            if (index == 0) return -1;
            return ((index + 1) >> 1) - 1;
        }

        private Tuple<int, int> GetChildrenIndex(int index)
        {
            var item2 = (index + 1) << 1;
            var item1 = item2 - 1;
            return Tuple.Create(item1, item2);
        }

        /// <summary>
        /// 優先度の一番大きい要素を取得し削除する
        /// O(logN)
        /// </summary>
        /// <returns>優先度の一番大きい要素</returns>
        /// <exception cref="IndexOutOfRangeException">キューが空だった場合</exception>
        public T Pop()
        {
            if (Count <= 0) throw new IndexOutOfRangeException();
            var item = _list[0].Item2;
            _list[0] = _list[Count - 1];
            _list.RemoveAt(Count - 1);

            int index = 0;
            Tuple<int, int> childrenIndex = GetChildrenIndex(index);
            while (childrenIndex.Item1 < Count || childrenIndex.Item2 < Count)
            {
                if (childrenIndex.Item2 >= Count || _list[childrenIndex.Item1].Item1 > _list[childrenIndex.Item2].Item1)
                {
                    if (_list[childrenIndex.Item1].Item1 <= _list[index].Item1) return item;

                    Swap(index, childrenIndex.Item1);
                    index = childrenIndex.Item1;
                }
                else
                {
                    if (_list[childrenIndex.Item2].Item1 <= _list[index].Item1) return item;
                    Swap(index, childrenIndex.Item2);
                    index = childrenIndex.Item2;
                }

                childrenIndex = GetChildrenIndex(index);
            }

            return item;
        }

        /// <summary>
        /// 優先度の一番大きい要素を取得する(削除はしない)
        /// O(1)
        /// </summary>
        /// <returns>優先度の一番大きい要素</returns>
        public T Peek()
        {
            return _list[0].Item2;
        }

        private void Swap(int index1, int index2)
        {
            var tmp = _list[index1];
            _list[index1] = _list[index2];
            _list[index2] = tmp;
        }
    }

    /// <summary>
    /// マージをサポートするUnionFind木
    /// </summary>
    public class UnionFind
    {
        private readonly int[] _array;

        /// <summary>
        /// 要素数を指定しUnionFind木を初期化する
        /// O(N)
        /// </summary>
        /// <param name="size">UnionFind木の要素数</param>
        public UnionFind(int size)
        {
            _array = new int[size];
            for (int i = 0; i < size; i++)
            {
                _array[i] = i;
            }
        }

        /// <summary>
        /// 根を取得する
        /// 同じグループに属する要素は同じ要素を根として持つ
        /// O(α(n))
        /// </summary>
        /// <param name="n">根を取得したい要素</param>
        /// <returns>根となる要素</returns>
        public int GetRootNode(int n)
        {
            if (_array[n] == n) return n;
            return _array[n] = GetRootNode(_array[n]);
        }

        /// <summary>
        /// ふたつの要素が所属するグループを併合する
        /// O(α(N))
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        public void UnionGroup(int a, int b)
        {
            var rootA = GetRootNode(a);
            var rootB = GetRootNode(b);
            if (rootA == rootB) return;
            _array[rootA] = rootB;
        }

        /// <summary>
        /// ふたつの要素が同じグループに所属するかを確かめる
        /// O(α(N))
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns>ふたつの要素が同じグループに所属する⇔true</returns>
        public bool IsSameGroup(int a, int b) => GetRootNode(a) == GetRootNode(b);
    }

    public delegate T SegTreeCombiner<T>(T item1, T item2);

    public class SegTree<T>
    {
        private readonly T _defaultItem;
        private readonly SegTreeCombiner<T> _func;
        private T[] List;
        private int size;

        public SegTree(T[] list, T defaultItem, SegTreeCombiner<T> func)
        {
            _defaultItem = defaultItem;
            _func = func;
            size = 1;
            while (size < list.Length) size <<= 1;
            List = new T[2 * size - 1];
            for (int i = 0; i           < list.Length; i++) List[i + size - 1] = list[i];
            for (int i = list.Length; i < size; i++) List[i        + size - 1] = defaultItem;
            for (int i = size - 1 - 1; i >= 0; i--)
            {
                List[i] = _func(List[2 * i + 1], List[2 * i + 2]);
            }
        }

        public void Update(int index, T value)
        {
            index += size - 1;
            List[index] = value;
            while (index > 0)
            {
                index = (index - 1) >> 1;
                List[index] = _func(List[2 * index + 1], List[2 * index + 2]);
            }
        }

        public T Query(int a, int b)
        {
            return Query(a, b, 0, 0, size);
        }

        private T Query(int a, int b, int k, int l, int r)
        {
            if (r <= a || b <= l) return _defaultItem;
            if (a <= l && r <= b) return List[k];
            return _func(Query(a, b, k * 2 + 1, l, (l + r) >> 1), Query(a, b, k * 2 + 2, (l + r) >> 1, r));
        }
    }

    public static class BinarySearch
    {
        public delegate bool Terms<T>(T i);

        public static int UpperBound(int initLeft, int initRight, Terms<int> term)
        {
            //TODO:範囲内に条件を満たす区間が存在しない場合に対応する
            int left = initLeft - 1, right = initRight;
            while (right - left > 1)
            {
                int mid = (left + right) >> 1;
                if (mid != initLeft - 1 && term(mid)) left = mid;
                else right = mid;
            }

            return left;
        }

        public static long UpperBound(long initLeft, long initRight, Terms<long> term)
        {
            long left = initLeft - 1, right = initRight;
            while (right - left > 1)
            {
                long mid = (left + right) >> 1;
                if (mid != initLeft - 1 && term(mid)) left = mid;
                else right = mid;
            }

            return left;
        }

        public static int LowerBound(int initLeft, int initRight, Terms<int> term)
        {
            int left = initLeft - 1, right = initRight;
            while (right - left > 1)
            {
                int mid = (left + right) >> 1;
                if (mid != initRight && term(mid)) right = mid;
                else left = mid;
            }

            return right;
        }

        public static long LowerBound(long initLeft, long initRight, Terms<long> term)
        {
            long left = initLeft - 1, right = initRight;
            while (right - left > 1)
            {
                long mid = (left + right) >> 1;
                if (term(mid)) right = mid;
                else left = mid;
            }

            return right;
        }
    }
}


struct ModInt
{
    public long Value { get; }
    public static long Mod { get; set; } = 1000000000L + 7;

    public ModInt(long l)
    {
        long value = l % Mod;
        while (value < 0) value += Mod;
        Value = value % Mod;
    }

    public static implicit operator long(ModInt m)
    {
        return m.Value;
    }

    public static implicit operator ModInt(long l)
    {
        return new ModInt(l);
    }

    public static ModInt operator +(ModInt a, ModInt b)
    {
        return new ModInt(a.Value + b.Value);
    }

    public static ModInt operator -(ModInt a, ModInt b)
    {
        return new ModInt(a.Value - b.Value);
    }

    public static ModInt operator *(ModInt a, ModInt b)
    {
        return new ModInt(a.Value * b.Value);
    }

    public static ModInt operator /(ModInt a, ModInt b)
    {
        return new ModInt(a * b.Inverse());
    }

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

static class ModIntMath
{
    public static ModInt Pow(this ModInt a, ModInt b)
    {
        var pow = b.Value;
        var m = a;
        ModInt ans = new ModInt(1);
        while (pow != 0)
        {
            if ((pow & 1) == 1)
            {
                ans *= m;
            }

            pow >>= 1;
            m *= m;
        }

        return ans;
    }

    public static ModInt Inverse(this ModInt m)
    {
        return m.Pow((ModInt) (ModInt.Mod - 2));
    }
}


namespace Extensions
{
    #region ConsoleExtensions

    public class ConsoleInputExtension
    {
        private static readonly ConsoleInputExtension _cin = new ConsoleInputExtension();

        public static ConsoleInputExtension Cin => _cin;

        private static readonly Queue<string> _inputQueue = new Queue<string>();

        private ConsoleInputExtension()
        {
        }

        public static implicit operator string(ConsoleInputExtension _)
        {
            if (_inputQueue.Count == 0)
                Console.ReadLine().Split(' ')
                       .ForEach(val => _inputQueue.Enqueue(val));
            return _inputQueue.Dequeue();
        }

        public static implicit operator int(ConsoleInputExtension _)
        {
            if (_inputQueue.Count == 0)
                Console.ReadLine().Split(' ')
                       .ForEach(val => _inputQueue.Enqueue(val));
            return int.Parse(_inputQueue.Dequeue());
        }

        public static implicit operator long(ConsoleInputExtension _)
        {
            if (_inputQueue.Count == 0)
                Console.ReadLine().Split(' ')
                       .ForEach(val => _inputQueue.Enqueue(val));
            return long.Parse(_inputQueue.Dequeue());
        }

        public static implicit operator float(ConsoleInputExtension _)
        {
            if (_inputQueue.Count == 0)
                Console.ReadLine().Split(' ')
                       .ForEach(val => _inputQueue.Enqueue(val));
            return float.Parse(_inputQueue.Dequeue());
        }

        public static implicit operator double(ConsoleInputExtension _)
        {
            if (_inputQueue.Count == 0)
                Console.ReadLine().Split(' ')
                       .ForEach(val => _inputQueue.Enqueue(val));
            return double.Parse(_inputQueue.Dequeue());
        }
    }

    public static class ConsoleOutputExtension
    {
        private static StringBuilder _builder = new StringBuilder();

        public static void Cout(object o)
        {
            _builder.Append(o);
        }

        public static void CoutF(object o)
        {
            _builder.Append(o);
            Flush();
        }

        public static void Coutln(object o)
        {
            _builder.Append(o);
            _builder.Append('\n');
        }

        public static void Coutln()
        {
            _builder.Append('\n');
        }

        public static void CoutlnF(object o)
        {
            _builder.Append(o);
            _builder.Append('\n');
            Flush();
        }

        public static void Flush()
        {
            Console.Write(_builder.ToString());
            _builder.Clear();
        }
    }

    #endregion

    public static class MathExtension
    {
        /// <summary>
        /// 最小公倍数を計算する
        /// O(log(max(num1,num2)))
        /// </summary>
        /// <param name="num1"></param>
        /// <param name="num2"></param>
        /// <returns>num1とnum2の最小公倍数</returns>
        public static int LCM(int num1, int num2)
        {
            var gcd = GCD(num1, num2);
            return num1 * (num2 / gcd);
        }

        /// <summary>
        /// 最小公倍数を計算する
        /// O(log(max(num1,num2)))
        /// </summary>
        /// <param name="num1"></param>
        /// <param name="num2"></param>
        /// <returns>num1とnum2の最小公倍数</returns>
        public static long LCM(long num1, long num2)
        {
            var gcd = GCD(num1, num2);
            return num1 * (num2 / gcd);
        }

        /// <summary>
        /// 最大公約数を計算する
        /// ただしnum1かnum2のどちらかが0の場合は0でない方を最小公倍数とする
        /// O(log(max(num1,num2)))
        /// </summary>
        /// <param name="num1"></param>
        /// <param name="num2"></param>
        /// <returns>num1とnum2の最大公約数</returns>
        public static int GCD(int num1, int num2)
        {
            int a = Math.Max(num1, num2);
            int b = Math.Min(num1, num2);
            if (b == 0) return a;
            int mod;
            while ((mod = a % b) != 0)
            {
                a = b;
                b = mod;
            }

            return b;
        }

        /// <summary>
        /// 最大公約数を計算する
        /// ただしnum1かnum2のどちらかが0の場合は0でない方を最小公倍数とする
        /// O(log(max(num1,num2)))
        /// </summary>
        /// <param name="num1"></param>
        /// <param name="num2"></param>
        /// <returns>num1とnum2の最大公約数</returns>
        public static long GCD(long num1, long num2)
        {
            long a = Math.Max(num1, num2);
            long b = Math.Min(num1, num2);
            if (b == 0) return a;
            long mod;
            while ((mod = a % b) != 0)
            {
                a = b;
                b = mod;
            }

            return b;
        }

        /// <summary>
        /// aのb乗を計算する
        /// O(log b)
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns>a^b</returns>
        public static long Pow(long a, long b)
        {
            checked
            {
                long p = 1;
                while (b != 0)
                {
                    if ((b & 1) == 1)
                    {
                        p *= a;
                    }

                    b >>= 1;
                    a *= a;
                }

                return p;
            }
        }

        /// <summary>
        /// aのb乗を計算する
        /// O(log b)
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns>a^b</returns>
        public static int Pow(int a, int b)
        {
            checked
            {
                int p = 1;
                while (b != 0)
                {
                    if ((b & 1) == 1)
                    {
                        p *= a;
                    }

                    b >>= 1;
                    a *= a;
                }

                return p;
            }
        }

        private static List<long> _nonPrimes;

        /// <summary>
        /// 引数の値が素数かどうか判定する
        /// 初回 O(searchLim log log searchLim)
        /// 以降 O(1)
        /// </summary>
        /// <param name="num">判定する値</param>
        /// <param name="searchLim">素数の探索範囲 num &lt;= searchLim プログラムの実行中通じて定数であることを想定</param>
        /// <returns></returns>
        public static bool IsPrime(int num, int searchLim)
        {
            _nonPrimes = _nonPrimes ?? new List<long>();
            if (_nonPrimes.Count >= (searchLim >> 6)) return ((_nonPrimes[num >> 6] >> (num & 0b111111)) & 1) == 0;
            searchLim = ((searchLim >> 6) + ((searchLim & 0b111111) != 0 ? 1 : 0)) << 6;

            _nonPrimes.AddRange(Enumerable.Repeat(0L, (searchLim >> 6) - _nonPrimes.Count));
            for (int i = 2; i < searchLim; i++)
            {
                if (((_nonPrimes[i >> 6] >> (i & 0b111111)) & 1) != 0) continue;
                int j = 2 * i;
                while (j < searchLim)
                {
                    _nonPrimes[j >> 6] |= 1L << (j & 0b111111);
                    j += i;
                }
            }

            return ((_nonPrimes[num >> 6] >> (num & 0b111111)) & 1) == 0;
        }

        /// <summary>
        /// 素因数分解する
        /// O(√num)
        /// </summary>
        /// <param name="num"></param>
        /// <returns>素因数とその個数のリスト</returns>
        public static List<(int prime, int count)> PrimeFactorization(long num)
        {
            var n = num;
            var primes = new List<(int, int)>();
            for (long i = 2; i * i <= num; i++)
            {
                int k = 0;
                while (n % i == 0)
                {
                    n /= i;
                    k++;
                }

                if (k == 0) continue;
                primes.Add(((int) i, k));
            }

            if (n > 1) primes.Add(((int) n, 1));
            return primes;
        }
    }

    public static class EnumerableExtension
    {
        public delegate void Function<in T>(T val);

        public static void ForEach<T>(this IEnumerable<T> enumerable, Function<T> function)
        {
            foreach (var x in enumerable)
            {
                function(x);
            }
        }
    }
}