// #define _GLIBCXX_DEBUG // for STL debug (optional)
#include <iostream>
#include <iomanip>
#include <cstdio>
#include <string>
#include <cstring>
#include <deque>
#include <list>
#include <queue>
#include <stack>
#include <vector>
#include <utility>
#include <algorithm>
#include <map>
#include <set>
#include <complex>
#include <cmath>
#include <limits>
#include <cfloat>
#include <climits>
#include <ctime>
#include <cassert>
#include <numeric>
#include <fstream>
#include <functional>
#include <bitset>
using namespace std;
using ll = long long int;
using int64 = long long int;
 
template<typename T> void chmax(T &a, T b) {a = max(a, b);}
template<typename T> void chmin(T &a, T b) {a = min(a, b);}
template<typename T> void chadd(T &a, T b) {a = a + b;}
 
int dx[] = {0, 0, 1, -1};
int dy[] = {1, -1, 0, 0};
const int INF = 1LL << 29;
const ll LONGINF = 1LL << 60;
const ll MOD = 1000000007LL;

// ・ミラーラビン (高速な素数判定)
// ・ロー法 (高速な素因数分解)
// ・どちらも確率的なアルゴリズムであるため、小さいものは通常の素数判定で処理したほうが確実
// Verified: POJ 2429 (GCD & LCM Inverse)

ll gcd(ll a, ll b) {
    assert(a >= 0 && b >= 0);
    if(a < b) swap(a, b);
    return (b == 0 ? a : gcd(b, a%b));
}

// 小さい数字は通常通りの素数判定で処理する (高速化のため)
const int MAX_PRIME = 300000;
const int MINI_PRIME = 40;
bool is_prime[MAX_PRIME + 10];
vector<int> primes, mini_primes;

void init_primes() {
    fill(is_prime, is_prime + MAX_PRIME + 10, true);
    is_prime[0] = is_prime[1] = false;
    for(int i=0; i<=MAX_PRIME; i++) {
        if(is_prime[i]) {
            if(i < MINI_PRIME) mini_primes.push_back(i);
            primes.push_back(i);
            for(int k=i+i; k<=MAX_PRIME; k+=i) {
                is_prime[k] = false;
            }
        }
    }
}

// ミラーラビン素数判定法 (Miller-Rabin Primality Test)
// 普通に掛け算するとオーバーフローで死ぬため、バイナリ法で処理
template<typename T>
struct MLPT {
    MLPT() {}

    T mod_mul(T a, T b, T mod) {
        T res = 0; a %= mod;
        while(b) {
            if(b & 1) (res += a) %= mod;
            a = (a + a) % mod;
            b >>= 1;
        }
        return res;
    }

    T mod_pow(T x, T n, T mod) {
        x %= mod;
        if(n == 0) return 1;
        T res = mod_pow(mod_mul(x, x, mod), n / 2, mod);
        if(n & 1) res = mod_mul(res, x, mod);
        return res;
    }

    // 小さい数なら素数判定テーブルをそのまま使う
    // 大きければしょうがないのでミラーラビンで
    bool solve(T N) {
        if(N <= MAX_PRIME) return is_prime[N];
        for(size_t i=0; i<mini_primes.size(); i++) {
            T a = mini_primes[i];
            if(N == a) return true;
            if(N % a == 0) return false;

            T d = N-1, s = 0;
            while(d % 2 == 0) d >>= 1, s++;

            if(mod_pow(a, d, N) == 1) continue;
            bool pbprime = false;
            for(int r=0; r<s; r++) {
                if(mod_pow(a, d*(1LL << r), N) == N-1) {
                    pbprime = true;
                    break;
                }
            }
            if(!pbprime) return false;
        }
        return true;
    }
};

// ロー法による素因数分解
// 対象となる数が素数の場合は常に failure -> Miller Rabin と併用しよう
template<typename T>
struct Rho {
    vector<int> pat;
    Rho() {}

    MLPT<T> ml;
    T func(T x, T c, T mod) {
        return (ml.mod_mul(x, x, mod) + c) % mod;
    }

    T check(T N, int c) {
        T x = 2, y = 2, d = 1;
        while(d == 1) {
            x = func(x, c, N);
            y = func(func(y, c, N), c, N);
            d = gcd((x-y>0 ? x-y : y-x), N);
        }
        return (d == N ? check(N, c+1) : d);
    }

    // ソートされてないので、必要だったら別途ソートしてね
    vector<T> factor(T N) {
        if(N < 2) return vector<T>();
        if(ml.solve(N)) return vector<T>(1, N);

        vector<T> res;
        // 小さい素因数は普通に素因数分解
        for(size_t i=0; i<mini_primes.size(); i++) {
            T a = mini_primes[i];
            if(a > N) break;
            while(N % a == 0) {
                res.push_back(a);
                N /= a;
            }
        }

        // 残ったやつはしょうがないのでロー法で
        if(N != 1) {
            if(ml.solve(N)) res.push_back(N);
            else {
                T D = check(N, 1);
                vector<T> va = factor(D), vb = factor(N / D);
                res.insert(res.end(), va.begin(), va.end());
                res.insert(res.end(), vb.begin(), vb.end());
            }
        }
        return res;
    }
};

namespace FastPrimeFactorization {

  template< typename word, typename dword, typename sword >
  struct UnsafeMod {
    UnsafeMod() : x(0) {}

    UnsafeMod(word _x) : x(init(_x)) {}

    bool operator==(const UnsafeMod &rhs) const {
      return x == rhs.x;
    }

    bool operator!=(const UnsafeMod &rhs) const {
      return x != rhs.x;
    }

    UnsafeMod &operator+=(const UnsafeMod &rhs) {
      if((x += rhs.x) >= mod) x -= mod;
      return *this;
    }

    UnsafeMod &operator-=(const UnsafeMod &rhs) {
      if(sword(x -= rhs.x) < 0) x += mod;
      return *this;
    }

    UnsafeMod &operator*=(const UnsafeMod &rhs) {
      x = reduce(dword(x) * rhs.x);
      return *this;
    }

    UnsafeMod operator+(const UnsafeMod &rhs) const {
      return UnsafeMod(*this) += rhs;
    }

    UnsafeMod operator-(const UnsafeMod &rhs) const {
      return UnsafeMod(*this) -= rhs;
    }

    UnsafeMod operator*(const UnsafeMod &rhs) const {
      return UnsafeMod(*this) *= rhs;
    }

    UnsafeMod pow(uint64_t e) const {
      UnsafeMod ret(1);
      for(UnsafeMod base = *this; e; e >>= 1, base *= base) {
        if(e & 1) ret *= base;
      }
      return ret;
    }

    word get() const {
      return reduce(x);
    }

    static constexpr int word_bits = sizeof(word) * 8;

    static word modulus() {
      return mod;
    }

    static word init(word w) {
      return reduce(dword(w) * r2);
    }

    static void set_mod(word m) {
      mod = m;
      inv = mul_inv(mod);
      r2 = -dword(mod) % mod;
    }

    static word reduce(dword x) {
      word y = word(x >> word_bits) - word((dword(word(x) * inv) * mod) >> word_bits);
      return sword(y) < 0 ? y + mod : y;
    }

    static word mul_inv(word n, int e = 6, word x = 1) {
      return !e ? x : mul_inv(n, e - 1, x * (2 - x * n));
    }

    static word mod, inv, r2;

    word x;
  };

  using uint128_t = __uint128_t;

  using Mod64 = UnsafeMod< uint64_t, uint128_t, int64_t >;
  template<> uint64_t Mod64::mod = 0;
  template<> uint64_t Mod64::inv = 0;
  template<> uint64_t Mod64::r2 = 0;

  using Mod32 = UnsafeMod< uint32_t, uint64_t, int32_t >;
  template<> uint32_t Mod32::mod = 0;
  template<> uint32_t Mod32::inv = 0;
  template<> uint32_t Mod32::r2 = 0;

  bool miller_rabin_primality_test_uint64(uint64_t n) {
    Mod64::set_mod(n);
    uint64_t d = n - 1;
    while(d % 2 == 0) d /= 2;
    Mod64 e{1}, rev{n - 1};
    // http://miller-rabin.appspot.com/  < 2^64
    for(uint64_t a : {2, 325, 9375, 28178, 450775, 9780504, 1795265022}) {
      if(n <= a) break;
      uint64_t t = d;
      Mod64 y = Mod64(a).pow(t);
      while(t != n - 1 && y != e && y != rev) {
        y *= y;
        t *= 2;
      }
      if(y != rev && t % 2 == 0) return false;
    }
    return true;
  }

  bool miller_rabin_primality_test_uint32(uint32_t n) {
    Mod32::set_mod(n);
    uint32_t d = n - 1;
    while(d % 2 == 0) d /= 2;
    Mod32 e{1}, rev{n - 1};
    for(uint32_t a : {2, 7, 61}) {
      if(n <= a) break;
      uint32_t t = d;
      Mod32 y = Mod32(a).pow(t);
      while(t != n - 1 && y != e && y != rev) {
        y *= y;
        t *= 2;
      }
      if(y != rev && t % 2 == 0) return false;
    }
    return true;
  }

  bool is_prime(uint64_t n) {
    if(n == 2) return true;
    if(n == 1 || n % 2 == 0) return false;
    if(n < uint64_t(1) << 31) return miller_rabin_primality_test_uint32(n);
    return miller_rabin_primality_test_uint64(n);
  }

  uint64_t pollard_rho(uint64_t n) {
    if(is_prime(n)) return n;
    if(n % 2 == 0) return 2;
    Mod64::set_mod(n);
    uint64_t d;
    Mod64 one{1};
    for(Mod64 c{one};; c += one) {
      Mod64 x{2}, y{2};
      do {
        x = x * x + c;
        y = y * y + c;
        y = y * y + c;
        d = __gcd((x - y).get(), n);
      } while(d == 1);
      if(d < n) return d;
    }
    assert(0);
  }

  vector< uint64_t > prime_factor(uint64_t n) {
    if(n <= 1) return {};
    uint64_t p = pollard_rho(n);
    if(p == n) return {p};
    auto l = prime_factor(p);
    auto r = prime_factor(n / p);
    copy(begin(r), end(r), back_inserter(l));
    return l;
  }
};

int main() {
    int N; scanf("%d", &N);

    vector<ll> A(N);
    for(int i=0; i<N; i++) scanf("%lld", &A[i]);

    /*
    init_primes();
    Rho<ll> rho;
    */
    
    ll cubic = 0;
    map< vector< pair<ll, ll> >, int > mp;
    for(int i=0; i<N; i++) {
        vector< pair<ll, ll> > vec;
        {
            auto primes = FastPrimeFactorization::prime_factor(A[i]);
            // rho.factor(A[i]);
            sort(primes.begin(), primes.end());

            vector< pair<ll, ll> > tmp;
            for(auto p : primes) {
                if(tmp.empty() or tmp.back().first != p) tmp.emplace_back(p, 1);
                else tmp.back().second++;
            }

            for(auto e : tmp) {
                if(e.second % 3 == 0) continue;
                e.second %= 3;
                vec.emplace_back(e);
            }

            if(vec.empty()) cubic++;
            else mp[vec]++;
        }
    }

    int ans = (cubic > 0);
    for(auto m : mp) {
        vector< pair<ll, ll> > v1 = m.first, v2;
        ll c1 = m.second, c2 = 0;
        if(c1 < 0) continue;
        
        for(auto e : v1) {
            v2.emplace_back(e.first, 3-e.second);
        }
        if(mp.count(v2)) c2 = mp[v2];

        ans += max(c1, c2);
        
        mp[v1] = -1;
        if(mp.count(v2)) mp[v2] = -1;
    }
    printf("%d\n", ans);
    return 0;
}


Measured memory_bytes: 15616