import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.StringTokenizer;

public class Div1_429C {

	static final long MOD = 1_000_000_007;
	static long[] fact = new long[305];
	static long[] iFact = new long[305];
	static final long I304 = 904487323;

	public static void main(String[] args) throws IOException {
		fact[0] = 1;
		for (int i = 1; i < 305; i++) {
			fact[i] = fact[i - 1] * i % MOD;
		}
		iFact[304] = I304;
		for (int i = 303; i >= 0; i--) {
			iFact[i] = iFact[i + 1] * (i + 1) % MOD;
		}

		BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
		PrintWriter printer = new PrintWriter(new BufferedWriter(new OutputStreamWriter(System.out)));
		int len = Integer.parseInt(reader.readLine());
		long[] groups = new long[len + 1];
		int[] gSizes = new int[len + 1];
		int nG = 0;

		StringTokenizer inputData = new StringTokenizer(reader.readLine());
		iLoop:
		for (int i = 0; i < len; i++) {
			long nxt = Integer.parseInt(inputData.nextToken());
			for (int j = 1; j <= nG; j++) {
				if (isSquare(nxt * groups[j])) {
					gSizes[j]++;
					continue iLoop;
				}
			}
			groups[++nG] = nxt;
			gSizes[nG] = 1;
		}

		long[][] dp = new long[nG + 1][len];

		dp[0][0] = 1;

		int fTotal = 0;
		for (int fG = 0; fG < nG; fG++) {
			for (int fB = 0; fB < len; fB++) {
				if (dp[fG][fB] == 0) {
					continue;
				}
				int nGSize = gSizes[fG + 1];
				for (int nS = 1; nS <= Math.min(nGSize, fTotal + 1); nS++) {
					for (int nBR = 0; nBR <= Math.min(fB, nS); nBR++) {
						long nW = dp[fG][fB] * fact[nGSize] % MOD * comb(nGSize - 1, nS - 1) % MOD * comb(fB, nBR) % MOD
								* comb(fTotal + 1 - fB, nS - nBR) % MOD;
						dp[fG + 1][fB - nBR + nGSize - nS] = (dp[fG + 1][fB - nBR + nGSize - nS] + nW) % MOD;
					}
				}
			}
			fTotal += gSizes[fG + 1];
		}
		printer.println(dp[nG][0]);
		printer.close();
	}

	static long comb(int a, int b) {
		if(b > a) {
			return 0;
		}
		return fact[a] * iFact[a - b] % MOD * iFact[b] % MOD;
	}

	static boolean isSquare(long inp) {
		long sqrt = (long) Math.sqrt(inp);
		return inp == sqrt * sqrt;
	}

}


import java.io.OutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.Arrays;
import java.io.BufferedWriter;
import java.io.Writer;
import java.io.OutputStreamWriter;
import java.util.InputMismatchException;
import java.io.IOException;
import java.io.InputStream;

/**
 * Built using CHelper plug-in
 * Actual solution is at the top
 */
public class Main {
    public static void main(String[] args) {
        InputStream inputStream = System.in;
        OutputStream outputStream = System.out;
        InputReader in = new InputReader(inputStream);
        OutputWriter out = new OutputWriter(outputStream);
        TaskC solver = new TaskC();
        solver.solve(1, in, out);
        out.close();
    }

    static class TaskC {
        public int mod = 1000000007;
        public int MAXN = 333;
        public int[][] w1;
        public long[] fact;
        public long[] ifact;

        public void solve(int testNumber, InputReader in, OutputWriter out) {
            long[][] e = Factorials.getFIF(MAXN, mod);
            fact = e[0];
            ifact = e[1];
            w1 = new int[MAXN][MAXN];
            w1[0][0] = 1;
            for (int i = 1; i < MAXN; i++) {
                for (int j = 1; j < MAXN; j++) {
                    for (int k = 1; k <= i; k++) {
                        w1[i][j] += w1[i - k][j - 1];
                        if (w1[i][j] >= mod) w1[i][j] -= mod;
                    }
                }
            }

            int n = in.nextInt();
            int[] arr = in.readIntArray(n);
            boolean[] marked = new boolean[n];
            int[] fs = new int[n];
            int fidx = 0;
            for (int i = 0; i < n; i++) {
                if (marked[i]) continue;
                int count = 0;
                for (int j = 0; j < n; j++) {
                    if (isSquare(1L * arr[i] * arr[j])) {
                        if (marked[j]) System.exit(1);
                        marked[j] = true;
                        count++;
                    }
                }
                fs[fidx++] = count;
            }

            fs = Arrays.copyOf(fs, fidx);
            long x = 1;
            for (int j : fs) x = x * fact[j] % mod;
            x = x * solve(fs) % mod;
            out.println(x);
        }

        public boolean isSquare(long x) {
            long d = (long) (Math.sqrt(x));
            while (d * d < x) d++;
            while (d * d > x) d--;
            return d * d == x;
        }

        public int solve(int[] freq) {
            int d = AUtils.sum(freq);
            int b = AUtils.max(freq);
            if (d == 0) return 1;
            if (b + b - 1 > d) return 0;
            int[] dp = new int[1];
            dp[0] = 1;
            for (int j = 0; j < freq.length; j++) {
                if (freq[j] == 0) continue;
                int[] nxt = new int[dp.length + freq[j]];
                for (int pgr = 0; pgr < dp.length; pgr++) {
                    for (int cgr = 1; cgr <= freq[j]; cgr++) {
                        nxt[pgr + cgr] += 1L * dp[pgr] * w1[freq[j]][cgr] % mod * ifact[cgr] % mod;
                        if (nxt[pgr + cgr] >= mod) nxt[pgr + cgr] -= mod;
                    }
                }
                dp = nxt;
            }

            int res = 0;
            for (int i = 0; i < dp.length; i++) {
                long x = 1L * dp[i] * fact[i] % mod;
                if ((d - i) % 2 == 0) res += x;
                else res -= x;
                if (res >= mod) res -= mod;
                if (res < 0) res += mod;
            }
            return res;
        }

    }

    static class InputReader {
        private InputStream stream;
        private byte[] buf = new byte[1024];
        private int curChar;
        private int numChars;

        public InputReader(InputStream stream) {
            this.stream = stream;
        }

        public int[] readIntArray(int tokens) {
            int[] ret = new int[tokens];
            for (int i = 0; i < tokens; i++) {
                ret[i] = nextInt();
            }
            return ret;
        }

        public int read() {
            if (this.numChars == -1) {
                throw new InputMismatchException();
            } else {
                if (this.curChar >= this.numChars) {
                    this.curChar = 0;

                    try {
                        this.numChars = this.stream.read(this.buf);
                    } catch (IOException var2) {
                        throw new InputMismatchException();
                    }

                    if (this.numChars <= 0) {
                        return -1;
                    }
                }

                return this.buf[this.curChar++];
            }
        }

        public int nextInt() {
            int c;
            for (c = this.read(); isSpaceChar(c); c = this.read()) {
                ;
            }

            byte sgn = 1;
            if (c == 45) {
                sgn = -1;
                c = this.read();
            }

            int res = 0;

            while (c >= 48 && c <= 57) {
                res *= 10;
                res += c - 48;
                c = this.read();
                if (isSpaceChar(c)) {
                    return res * sgn;
                }
            }

            throw new InputMismatchException();
        }

        public static boolean isSpaceChar(int c) {
            return c == 32 || c == 10 || c == 13 || c == 9 || c == -1;
        }

    }

    static class AUtils {
        public static int max(int[] arr) {
            int res = arr[0];
            for (int x : arr) res = Math.max(res, x);
            return res;
        }

        public static int sum(int[] arr) {
            int sum = 0;
            for (int x : arr) {
                sum += x;
            }
            return sum;
        }

    }

    static class Factorials {
        public static long[][] getFIF(int max, int mod) {
            long[] fact = new long[max];
            long[] ifact = new long[max];
            long[] inv = new long[max];
            inv[1] = 1;
            for (int i = 2; i < max; i++) {
                inv[i] = (mod - mod / i) * inv[mod % i] % mod;
            }
            fact[0] = 1;
            ifact[0] = 1;
            for (int i = 1; i < max; i++) {
                fact[i] = fact[i - 1] * i % mod;
                ifact[i] = ifact[i - 1] * inv[i] % mod;
            }
            return new long[][]{fact, ifact, inv};
        }

    }

    static class OutputWriter {
        private final PrintWriter writer;

        public OutputWriter(OutputStream outputStream) {
            writer = new PrintWriter(new BufferedWriter(new OutputStreamWriter(outputStream)));
        }

        public OutputWriter(Writer writer) {
            this.writer = new PrintWriter(writer);
        }

        public void close() {
            writer.close();
        }

        public void println(long i) {
            writer.println(i);
        }

    }
}


import java.io.OutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.util.Arrays;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.io.Closeable;
import java.io.Writer;
import java.io.OutputStreamWriter;
import java.io.InputStream;

/**
 * Built using CHelper plug-in
 * Actual solution is at the top
 */
public class Main {
    public static void main(String[] args) throws Exception {
        Thread thread = new Thread(null, new TaskAdapter(), "", 1 << 27);
        thread.start();
        thread.join();
    }

    static class TaskAdapter implements Runnable {
        @Override
        public void run() {
            InputStream inputStream = System.in;
            OutputStream outputStream = System.out;
            FastInput in = new FastInput(inputStream);
            FastOutput out = new FastOutput(outputStream);
            COnTheBench solver = new COnTheBench();
            solver.solve(1, in, out);
            out.close();
        }
    }

    static class COnTheBench {
        Modular mod = new Modular(1e9 + 7);
        Factorial fact = new Factorial(1000, mod);
        Combination comb = new Combination(fact);
        Debug debug = new Debug(true);

        public int f(int i, int j) {
            int ans = mod.mul(fact.fact(i), comb.combination(i + j - 1 - j, j - 1));
            ans = mod.mul(ans, fact.invFact(j));
            return ans;
        }

        public void solve(int testNumber, FastInput in, FastOutput out) {
            int n = in.readInt();
            DSU dsu = new DSU(n);
            long[] a = new long[n];
            for (int i = 0; i < n; i++) {
                a[i] = in.readInt();
            }
            for (int i = 0; i < n; i++) {
                for (int j = 0; j < i; j++) {
                    if (square(a[j] * a[i])) {
                        dsu.merge(j, i);
                        break;
                    }
                }
            }

            IntegerList list = new IntegerList();
            for (int i = 0; i < n; i++) {
                if (dsu.find(i) == i) {
                    list.add(dsu.size[dsu.find(i)]);
                }
            }

            int[] cnts = list.toArray();
            debug.debug("cnts", cnts);
            int m = cnts.length;
            int[][] dp = new int[m + 1][n + 1];
            dp[0][0] = 1;
            for (int i = 1; i <= m; i++) {
                int way = cnts[i - 1];
                for (int j = 0; j <= n; j++) {
                    dp[i][j] = 0;
                    for (int k = 1; k <= j && k <= way; k++) {
                        int contrib = mod.mul(f(way, k), dp[i - 1][j - k]);
                        dp[i][j] = mod.plus(dp[i][j], contrib);
                    }
                }
            }
            debug.debug("dp", dp);

            int ans = 0;
            for (int i = 0; i <= n; i++) {
                int local = mod.mul(dp[m][i], fact.fact(i));
                if ((n - i) % 2 == 1) {
                    local = mod.valueOf(-local);
                }
                ans = mod.plus(ans, local);
            }

            out.println(ans);
        }

        public boolean square(long x) {
            long l = 1;
            long r = (long) 1e9;
            while (l < r) {
                long m = (l + r) / 2;
                if (m * m < x) {
                    l = m + 1;
                } else {
                    r = m;
                }
            }
            return l * l == x;
        }

    }

    static class IntegerList implements Cloneable {
        private int size;
        private int cap;
        private int[] data;
        private static final int[] EMPTY = new int[0];

        public IntegerList(int cap) {
            this.cap = cap;
            if (cap == 0) {
                data = EMPTY;
            } else {
                data = new int[cap];
            }
        }

        public IntegerList(IntegerList list) {
            this.size = list.size;
            this.cap = list.cap;
            this.data = Arrays.copyOf(list.data, size);
        }

        public IntegerList() {
            this(0);
        }

        public void ensureSpace(int req) {
            if (req > cap) {
                while (cap < req) {
                    cap = Math.max(cap + 10, 2 * cap);
                }
                data = Arrays.copyOf(data, cap);
            }
        }

        public void add(int x) {
            ensureSpace(size + 1);
            data[size++] = x;
        }

        public void addAll(int[] x, int offset, int len) {
            ensureSpace(size + len);
            System.arraycopy(x, offset, data, size, len);
            size += len;
        }

        public void addAll(IntegerList list) {
            addAll(list.data, 0, list.size);
        }

        public int[] toArray() {
            return Arrays.copyOf(data, size);
        }

        public String toString() {
            return Arrays.toString(toArray());
        }

        public boolean equals(Object obj) {
            if (!(obj instanceof IntegerList)) {
                return false;
            }
            IntegerList other = (IntegerList) obj;
            return SequenceUtils.equal(data, 0, size - 1, other.data, 0, other.size - 1);
        }

        public int hashCode() {
            int h = 1;
            for (int i = 0; i < size; i++) {
                h = h * 31 + Integer.hashCode(data[i]);
            }
            return h;
        }

        public IntegerList clone() {
            IntegerList ans = new IntegerList();
            ans.addAll(this);
            return ans;
        }

    }

    static class Combination implements IntCombination {
        final Factorial factorial;
        final Modular modular;

        public Combination(Factorial factorial) {
            this.factorial = factorial;
            this.modular = factorial.getModular();
        }

        public Combination(int limit, Modular modular) {
            this(new Factorial(limit, modular));
        }

        public int combination(int m, int n) {
            if (n > m) {
                return 0;
            }
            return modular.mul(modular.mul(factorial.fact(m), factorial.invFact(n)), factorial.invFact(m - n));
        }

    }

    static class FastInput {
        private final InputStream is;
        private byte[] buf = new byte[1 << 13];
        private int bufLen;
        private int bufOffset;
        private int next;

        public FastInput(InputStream is) {
            this.is = is;
        }

        private int read() {
            while (bufLen == bufOffset) {
                bufOffset = 0;
                try {
                    bufLen = is.read(buf);
                } catch (IOException e) {
                    bufLen = -1;
                }
                if (bufLen == -1) {
                    return -1;
                }
            }
            return buf[bufOffset++];
        }

        public void skipBlank() {
            while (next >= 0 && next <= 32) {
                next = read();
            }
        }

        public int readInt() {
            int sign = 1;

            skipBlank();
            if (next == '+' || next == '-') {
                sign = next == '+' ? 1 : -1;
                next = read();
            }

            int val = 0;
            if (sign == 1) {
                while (next >= '0' && next <= '9') {
                    val = val * 10 + next - '0';
                    next = read();
                }
            } else {
                while (next >= '0' && next <= '9') {
                    val = val * 10 - next + '0';
                    next = read();
                }
            }

            return val;
        }

    }

    static interface IntCombination {
    }

    static class FastOutput implements AutoCloseable, Closeable, Appendable {
        private StringBuilder cache = new StringBuilder(10 << 20);
        private final Writer os;

        public FastOutput append(CharSequence csq) {
            cache.append(csq);
            return this;
        }

        public FastOutput append(CharSequence csq, int start, int end) {
            cache.append(csq, start, end);
            return this;
        }

        public FastOutput(Writer os) {
            this.os = os;
        }

        public FastOutput(OutputStream os) {
            this(new OutputStreamWriter(os));
        }

        public FastOutput append(char c) {
            cache.append(c);
            return this;
        }

        public FastOutput append(int c) {
            cache.append(c);
            return this;
        }

        public FastOutput println(int c) {
            return append(c).println();
        }

        public FastOutput println() {
            cache.append(System.lineSeparator());
            return this;
        }

        public FastOutput flush() {
            try {
                os.append(cache);
                os.flush();
                cache.setLength(0);
            } catch (IOException e) {
                throw new UncheckedIOException(e);
            }
            return this;
        }

        public void close() {
            flush();
            try {
                os.close();
            } catch (IOException e) {
                throw new UncheckedIOException(e);
            }
        }

        public String toString() {
            return cache.toString();
        }

    }

    static class InverseNumber {
        int[] inv;

        public InverseNumber(int[] inv, int limit, Modular modular) {
            this.inv = inv;
            inv[1] = 1;
            int p = modular.getMod();
            for (int i = 2; i <= limit; i++) {
                int k = p / i;
                int r = p % i;
                inv[i] = modular.mul(-k, inv[r]);
            }
        }

        public InverseNumber(int limit, Modular modular) {
            this(new int[limit + 1], limit, modular);
        }

    }

    static class Debug {
        private boolean offline;
        private PrintStream out = System.err;
        static int[] empty = new int[0];

        public Debug(boolean enable) {
            offline = enable && System.getSecurityManager() == null;
        }

        public Debug debug(String name, Object x) {
            return debug(name, x, empty);
        }

        public Debug debug(String name, Object x, int... indexes) {
            if (offline) {
                if (x == null || !x.getClass().isArray()) {
                    out.append(name);
                    for (int i : indexes) {
                        out.printf("[%d]", i);
                    }
                    out.append("=").append("" + x);
                    out.println();
                } else {
                    indexes = Arrays.copyOf(indexes, indexes.length + 1);
                    if (x instanceof byte[]) {
                        byte[] arr = (byte[]) x;
                        for (int i = 0; i < arr.length; i++) {
                            indexes[indexes.length - 1] = i;
                            debug(name, arr[i], indexes);
                        }
                    } else if (x instanceof short[]) {
                        short[] arr = (short[]) x;
                        for (int i = 0; i < arr.length; i++) {
                            indexes[indexes.length - 1] = i;
                            debug(name, arr[i], indexes);
                        }
                    } else if (x instanceof boolean[]) {
                        boolean[] arr = (boolean[]) x;
                        for (int i = 0; i < arr.length; i++) {
                            indexes[indexes.length - 1] = i;
                            debug(name, arr[i], indexes);
                        }
                    } else if (x instanceof char[]) {
                        char[] arr = (char[]) x;
                        for (int i = 0; i < arr.length; i++) {
                            indexes[indexes.length - 1] = i;
                            debug(name, arr[i], indexes);
                        }
                    } else if (x instanceof int[]) {
                        int[] arr = (int[]) x;
                        for (int i = 0; i < arr.length; i++) {
                            indexes[indexes.length - 1] = i;
                            debug(name, arr[i], indexes);
                        }
                    } else if (x instanceof float[]) {
                        float[] arr = (float[]) x;
                        for (int i = 0; i < arr.length; i++) {
                            indexes[indexes.length - 1] = i;
                            debug(name, arr[i], indexes);
                        }
                    } else if (x instanceof double[]) {
                        double[] arr = (double[]) x;
                        for (int i = 0; i < arr.length; i++) {
                            indexes[indexes.length - 1] = i;
                            debug(name, arr[i], indexes);
                        }
                    } else if (x instanceof long[]) {
                        long[] arr = (long[]) x;
                        for (int i = 0; i < arr.length; i++) {
                            indexes[indexes.length - 1] = i;
                            debug(name, arr[i], indexes);
                        }
                    } else {
                        Object[] arr = (Object[]) x;
                        for (int i = 0; i < arr.length; i++) {
                            indexes[indexes.length - 1] = i;
                            debug(name, arr[i], indexes);
                        }
                    }
                }
            }
            return this;
        }

    }

    static class Factorial {
        int[] fact;
        int[] inv;
        Modular modular;

        public Modular getModular() {
            return modular;
        }

        public Factorial(int[] fact, int[] inv, InverseNumber in, int limit, Modular modular) {
            this.modular = modular;
            this.fact = fact;
            this.inv = inv;
            fact[0] = inv[0] = 1;
            for (int i = 1; i <= limit; i++) {
                fact[i] = modular.mul(fact[i - 1], i);
                inv[i] = modular.mul(inv[i - 1], in.inv[i]);
            }
        }

        public Factorial(int limit, Modular modular) {
            this(new int[limit + 1], new int[limit + 1], new InverseNumber(limit, modular), limit, modular);
        }

        public int fact(int n) {
            return fact[n];
        }

        public int invFact(int n) {
            return inv[n];
        }

    }

    static class DSU {
        protected int[] p;
        protected int[] rank;
        int[] size;

        public DSU(int n) {
            p = new int[n];
            rank = new int[n];
            size = new int[n];
            reset();
        }

        public final void reset() {
            for (int i = 0; i < p.length; i++) {
                p[i] = i;
                rank[i] = 0;
                size[i] = 1;
            }
        }

        public final int find(int a) {
            if (p[a] == p[p[a]]) {
                return p[a];
            }
            return p[a] = find(p[a]);
        }

        public final void merge(int a, int b) {
            a = find(a);
            b = find(b);
            if (a == b) {
                return;
            }
            if (rank[a] == rank[b]) {
                rank[a]++;
            }

            if (rank[a] < rank[b]) {
                int tmp = a;
                a = b;
                b = tmp;
            }

            size[a] += size[b];
            p[b] = a;
        }

    }

    static class Modular {
        int m;

        public int getMod() {
            return m;
        }

        public Modular(int m) {
            this.m = m;
        }

        public Modular(long m) {
            this.m = (int) m;
            if (this.m != m) {
                throw new IllegalArgumentException();
            }
        }

        public Modular(double m) {
            this.m = (int) m;
            if (this.m != m) {
                throw new IllegalArgumentException();
            }
        }

        public int valueOf(int x) {
            x %= m;
            if (x < 0) {
                x += m;
            }
            return x;
        }

        public int valueOf(long x) {
            x %= m;
            if (x < 0) {
                x += m;
            }
            return (int) x;
        }

        public int mul(int x, int y) {
            return valueOf((long) x * y);
        }

        public int plus(int x, int y) {
            return valueOf(x + y);
        }

        public String toString() {
            return "mod " + m;
        }

    }

    static class SequenceUtils {
        public static boolean equal(int[] a, int al, int ar, int[] b, int bl, int br) {
            if ((ar - al) != (br - bl)) {
                return false;
            }
            for (int i = al, j = bl; i <= ar; i++, j++) {
                if (a[i] != b[j]) {
                    return false;
                }
            }
            return true;
        }

    }
}


/**
 * author: derrick20
 * created: 11/11/20 1:03 PM
 */
import java.io.*;
import java.util.*;
import static java.lang.Math.*;

public class OnTheBench {
    public static void main(String[] args) {
        setupCombo(301);
        FastScanner sc = new FastScanner();
        PrintWriter out = new PrintWriter(System.out);

        int N = sc.nextInt();
        long[] a = sc.nextLongs(N);
        boolean[] vis = new boolean[N];
        int[] groups = new int[N + 1];
        int G = 0;
        for (int i = 0; i < N; i++) {
            if (!vis[i]) {
                vis[i] = true;
                int elems = 1;
                for (int j = i + 1; j < N; j++) {
                    long prod = a[i] * a[j];
                    long root = (long) Math.sqrt(prod);
                    if (!vis[j] && prod == root * root) {
                        vis[j] = true;
                        elems++;
                    }
                }
                groups[++G] = elems;
            }
        }

        long[][] dp = new long[G + 1][N + 1];
        // dp[g][bad] = ways to interleave first g groups for a given # of bad pairs
        dp[0][0] = 1;
        // dp[0][k] = 0, k != 0
        int total = 0;
        for (int prefix = 1; prefix <= G; prefix++) {
            int amt = groups[prefix];
            for (int prevBad = 0; prevBad <= max(0, total - 1); prevBad++) {
                for (int fixed = 0; fixed <= min(prevBad, amt); fixed++) {
                    for (int slots = max(1, fixed); slots <= min(amt, total + 1); slots++) {
                        int introduced = amt - slots;
                        long ways = mult(
                                choose[prevBad][fixed],
                                choose[total + 1 - prevBad][slots - fixed],
                                choose[amt - 1][slots - 1],
                                fact[amt],
                                dp[prefix - 1][prevBad]
                        );
                        int currBad = prevBad + introduced - fixed;
                        dp[prefix][currBad] = (dp[prefix][currBad] + ways) % mod;
                    }
                }
            }
            total += amt;
//            System.out.println(Arrays.toString(dp[prefix]));
        }
        out.println(dp[G][0]);
        out.close();
    }

    static long mod = (long) 1e9 + 7;
    static long[][] choose;
    static long[] fact;

    static long mult(long... multiplicands) {
        long ans = 1;
        for (long v : multiplicands) {
            ans = (ans * v) % mod;
        }
        return ans;
    }

    static void setupCombo(int MAX) {
        choose = new long[MAX + 1][MAX + 1];
        fact = new long[MAX + 1];
        choose[0][0] = 1;
        fact[0] = 1;
        for (int i = 1; i <= MAX; i++) {
            fact[i] = (long) i * fact[i - 1] % mod;
            choose[i][0] = 1;
            for (int j = 1; j < i; j++) {
                choose[i][j] = (choose[i - 1][j - 1] + choose[i - 1][j]) % mod;
            }
            choose[i][i] = 1;
        }
    }

    static class FastScanner {
        private int BS = 1 << 16;
        private char NC = (char) 0;
        private byte[] buf = new byte[BS];
        private int bId = 0, size = 0;
        private char c = NC;
        private double cnt = 1;
        private BufferedInputStream in;

        public FastScanner() {
            in = new BufferedInputStream(System.in, BS);
        }

        public FastScanner(String s) {
            try {
                in = new BufferedInputStream(new FileInputStream(new File(s)), BS);
            } catch (Exception e) {
                in = new BufferedInputStream(System.in, BS);
            }
        }

        private char getChar() {
            while (bId == size) {
                try {
                    size = in.read(buf);
                } catch (Exception e) {
                    return NC;
                }
                if (size == -1) return NC;
                bId = 0;
            }
            return (char) buf[bId++];
        }

        public int nextInt() {
            return (int) nextLong();
        }

        public int[] nextInts(int N) {
            int[] res = new int[N];
            for (int i = 0; i < N; i++) {
                res[i] = (int) nextLong();
            }
            return res;
        }

        public long[] nextLongs(int N) {
            long[] res = new long[N];
            for (int i = 0; i < N; i++) {
                res[i] = nextLong();
            }
            return res;
        }

        public long nextLong() {
            cnt = 1;
            boolean neg = false;
            if (c == NC) c = getChar();
            for (; (c < '0' || c > '9'); c = getChar()) {
                if (c == '-') neg = true;
            }
            long res = 0;
            for (; c >= '0' && c <= '9'; c = getChar()) {
                res = (res << 3) + (res << 1) + c - '0';
                cnt *= 10;
            }
            return neg ? -res : res;
        }

        public double nextDouble() {
            double cur = nextLong();
            return c != '.' ? cur : cur + nextLong() / cnt;
        }

        public double[] nextDoubles(int N) {
            double[] res = new double[N];
            for (int i = 0; i < N; i++) {
                res[i] = nextDouble();
            }
            return res;
        }

        public String next() {
            StringBuilder res = new StringBuilder();
            while (c <= 32) c = getChar();
            while (c > 32) {
                res.append(c);
                c = getChar();
            }
            return res.toString();
        }

        public String nextLine() {
            StringBuilder res = new StringBuilder();
            while (c <= 32) c = getChar();
            while (c != '\n') {
                res.append(c);
                c = getChar();
            }
            return res.toString();
        }

        public boolean hasNext() {
            if (c > 32) return true;
            while (true) {
                c = getChar();
                if (c == NC) return false;
                else if (c > 32) return true;
            }
        }
    }
    static void ASSERT(boolean assertion, String message) {
        if (!assertion) throw new AssertionError(message);
    }
    static void ASSERT(boolean assertion) {
        if (!assertion) throw new AssertionError();
    }
}


/**
 * author: derrick20
 * created: 11/11/20 1:45 PM
 */
import java.io.*;
import java.util.*;
import static java.lang.Math.*;

public class OnTheBenchAlt {
    public static void main(String[] args) {
        setupCombo(301);
        FastScanner sc = new FastScanner();
        PrintWriter out = new PrintWriter(System.out);

        int N = sc.nextInt();
        long[] a = new long[N];
        HashMap<Long, Integer> clusters = new HashMap<>();
        for (int i = 0; i < N; i++) {
            a[i] = removeSquares(sc.nextLong());
            clusters.merge(a[i], 1, Integer::sum);
        }
        int G = clusters.size();
        int[] groups = new int[G + 1];
        int ptr = 1;
        for (int amt : clusters.values()) {
            groups[ptr++] = amt;
        }
        long[][] dp = new long[G + 1][N + 1];
        // dp[g][bad] = ways to interleave first g groups for a given # of bad pairs
        dp[0][0] = 1;
        // dp[0][k] = 0, k != 0
        int total = 0;
        /*
        intuition for runtime analysis:
        Say there were k groups, each size n / k

        The outer loop is k, 2nd loop is n worst case,
        and the inner 2 are bounded by n / k (amt per group)
        k * n * (n / k) * (n / k) = n^3 / k, which works despite 4 loops!!

        Seems hard to convert into pull-dp since prevBad has a more direct
        meaning in the arrangements, so using that as our variable makes more sense
         */
        for (int prefix = 1; prefix <= G; prefix++) {
            int amt = groups[prefix];
            // key bugs here and there: USE THE CORRECT BOUNDS
            for (int prevBad = 0; prevBad <= max(0, total - 1); prevBad++) {
                for (int fixed = 0; fixed <= min(prevBad, amt); fixed++) {
                    for (int slots = max(1, fixed); slots <= min(amt, total + 1); slots++) {
                        int introduced = amt - slots;
                        long ways = mult(
                                choose[prevBad][fixed],
                                choose[total + 1 - prevBad][slots - fixed],
                                choose[amt - 1][slots - 1],
                                fact[amt],
                                dp[prefix - 1][prevBad] // key bug: NEED TO RELATE PREVIOUS DP
                        );
                        int currBad = prevBad + introduced - fixed;
                        dp[prefix][currBad] = (dp[prefix][currBad] + ways) % mod;
                    }
                }
            }
            total += amt;
//            System.out.println(Arrays.toString(dp[prefix]));
        }
        out.println(dp[G][0]);
        out.close();
    }

    static long mod = (long) 1e9 + 7;
    static long[][] choose;
    static long[] fact;

    static long removeSquares(long x) {
        long curr = x;
        for (long v = 2; v * v <= x && curr > 1; v++) {
            int cnt = 0;
            while (curr % v == 0) {
                curr /= v;
                cnt ^= 1;
            }
            if (cnt == 1)
                curr *= v;
        }
        return curr;
    }

    static long choose(int n, int k) {
        return k < 0 || k > n ? 0 : choose[n][k];
    }

    static long mult(long... multiplicands) {
        long ans = 1;
        for (long v : multiplicands) {
            ans = (ans * v) % mod;
        }
        return ans;
    }

    static void setupCombo(int MAX) {
        choose = new long[MAX + 1][MAX + 1];
        fact = new long[MAX + 1];
        choose[0][0] = 1;
        fact[0] = 1;
        for (int i = 1; i <= MAX; i++) {
            fact[i] = (long) i * fact[i - 1] % mod;
            choose[i][0] = 1;
            for (int j = 1; j < i; j++) {
                choose[i][j] = (choose[i - 1][j - 1] + choose[i - 1][j]) % mod;
            }
            choose[i][i] = 1;
        }
    }

    static class FastScanner {
        private int BS = 1 << 16;
        private char NC = (char) 0;
        private byte[] buf = new byte[BS];
        private int bId = 0, size = 0;
        private char c = NC;
        private double cnt = 1;
        private BufferedInputStream in;

        public FastScanner() {
            in = new BufferedInputStream(System.in, BS);
        }

        public FastScanner(String s) {
            try {
                in = new BufferedInputStream(new FileInputStream(new File(s)), BS);
            } catch (Exception e) {
                in = new BufferedInputStream(System.in, BS);
            }
        }

        private char getChar() {
            while (bId == size) {
                try {
                    size = in.read(buf);
                } catch (Exception e) {
                    return NC;
                }
                if (size == -1) return NC;
                bId = 0;
            }
            return (char) buf[bId++];
        }

        public int nextInt() {
            return (int) nextLong();
        }

        public int[] nextInts(int N) {
            int[] res = new int[N];
            for (int i = 0; i < N; i++) {
                res[i] = (int) nextLong();
            }
            return res;
        }

        public long[] nextLongs(int N) {
            long[] res = new long[N];
            for (int i = 0; i < N; i++) {
                res[i] = nextLong();
            }
            return res;
        }

        public long nextLong() {
            cnt = 1;
            boolean neg = false;
            if (c == NC) c = getChar();
            for (; (c < '0' || c > '9'); c = getChar()) {
                if (c == '-') neg = true;
            }
            long res = 0;
            for (; c >= '0' && c <= '9'; c = getChar()) {
                res = (res << 3) + (res << 1) + c - '0';
                cnt *= 10;
            }
            return neg ? -res : res;
        }

        public double nextDouble() {
            double cur = nextLong();
            return c != '.' ? cur : cur + nextLong() / cnt;
        }

        public double[] nextDoubles(int N) {
            double[] res = new double[N];
            for (int i = 0; i < N; i++) {
                res[i] = nextDouble();
            }
            return res;
        }

        public String next() {
            StringBuilder res = new StringBuilder();
            while (c <= 32) c = getChar();
            while (c > 32) {
                res.append(c);
                c = getChar();
            }
            return res.toString();
        }

        public String nextLine() {
            StringBuilder res = new StringBuilder();
            while (c <= 32) c = getChar();
            while (c != '\n') {
                res.append(c);
                c = getChar();
            }
            return res.toString();
        }

        public boolean hasNext() {
            if (c > 32) return true;
            while (true) {
                c = getChar();
                if (c == NC) return false;
                else if (c > 32) return true;
            }
        }
    }
    static void ASSERT(boolean assertion, String message) {
        if (!assertion) throw new AssertionError(message);
    }
    static void ASSERT(boolean assertion) {
        if (!assertion) throw new AssertionError();
    }
}


import java.io.OutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.StringTokenizer;
import java.util.HashMap;
import java.io.IOException;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.io.InputStream;

/**
 * Built using CHelper plug-in
 * Actual solution is at the top
 */
public class Main {
    public static void main(String[] args) {
        InputStream inputStream = System.in;
        OutputStream outputStream = System.out;
        InputReader in = new InputReader(inputStream);
        PrintWriter out = new PrintWriter(outputStream);
        OnTheBench solver = new OnTheBench();
        solver.solve(1, in, out);
        out.close();
    }

    static class OnTheBench {
        long MOD = (long) (1e9) + 7;
        long[][] C = new long[333][333];

        public void solve(int testNumber, InputReader in, PrintWriter out) {
            int N = in.nextInt();
            DisjointSet dsu = new DisjointSet(N);
            long[] arr = new long[N];
            setC();
            for (int i = 0; i < N; i++) {
                arr[i] = in.nextInt();
            }
            for (int i = 0; i < N; i++) {
                for (int j = i + 1; j < N; j++) {
                    long sqrt = (long) (Math.sqrt(arr[i] * arr[j]));
                    long sqrt2 = (long) (Math.ceil(Math.sqrt(arr[i] * arr[j])));
                    if (sqrt * sqrt == arr[i] * arr[j] || sqrt2 * sqrt2 == arr[i] * arr[j]) {
                        dsu.merge(i, j);
                    }
                }
            }
            ArrayList<Integer> sz = new ArrayList<>();
            sz.add(0);
            HashMap<Integer, Integer> seen = new HashMap<>();
            long mult = 1;
            for (int i = 0; i < N; i++) {
                if (!seen.containsKey(dsu.find(i))) {
                    seen.put(dsu.find(i), sz.size());
                    sz.add(0);
                }
                sz.set(seen.get(dsu.find(i)), sz.get(seen.get(dsu.find(i))) + 1);
            }
            for (int i : sz) {
//            if (arr[0] == 285) {
//                out.println(i);
//            }
                mult *= fact(i);
                mult %= MOD;
            }
            long[][] dp = new long[sz.size()][333];
            int sum = 0;
            dp[0][0] = 1;
            for (int n = 1; n < dp.length; n++) {
                for (int ij = 1; ij <= sz.get(n); ij++) {
                    for (int y = 0; y <= sum; y++) {
                        for (int j = 0; j <= Math.min(y, ij); j++) {
                            int i = ij - j;
                            dp[n][y - j + sz.get(n) - ij] += ((((dp[n - 1][y] * C[sum + 1 - y][i]) % MOD * C[y][j]) % MOD) * C[sz.get(n) - 1][ij - 1]) % MOD;
                            dp[n][y - j + sz.get(n) - ij] %= MOD;
                        }
                    }
                }
                sum += sz.get(n);
            }
            out.println((dp[sz.size() - 1][0] * mult) % MOD);
        }

        void setC() {
            for (int i = 0; i <= 332; i++) {
                C[i][0] = 1;
                for (int j = 1; j <= i; j++) {
                    C[i][j] = C[i - 1][j] + C[i - 1][j - 1];
                    C[i][j] %= MOD;
                }
            }
        }

        long fact(int i) {
            long res = 1;
            while (i > 0) {
                res *= i;
                res %= MOD;
                i--;
            }
            return res;
        }

    }

    static class InputReader {
        public BufferedReader reader;
        public StringTokenizer tokenizer;

        public InputReader(InputStream stream) {
            reader = new BufferedReader(new InputStreamReader(stream), 32768);
            tokenizer = null;
        }

        public String next() {
            while (tokenizer == null || !tokenizer.hasMoreTokens()) {
                try {
                    tokenizer = new StringTokenizer(reader.readLine());
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
            return tokenizer.nextToken();
        }

        public int nextInt() {
            return Integer.parseInt(next());
        }

    }

    static class DisjointSet {
        int[] rank;
        int[] par;

        public DisjointSet(int N) {
            rank = new int[N];
            par = new int[N];
            for (int i = 0; i < N; i++) {
                rank[i] = 1;
                par[i] = i;
            }
        }

        public int find(int x) {
            if (x == par[x]) {
                return x;
            }
            return (par[x] = find(par[x]));
        }

        public void merge(int x, int y) {
            int parX = find(x);
            int parY = find(y);
            if (parX != parY) {
                if (rank[parX] > rank[parY]) {
                    par[parY] = parX;
                    rank[parX] += rank[parY];
                } else {
                    par[parX] = parY;
                    rank[parY] += rank[parX];
                }
            }
        }

    }
}


import java.io.OutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.Iterator;
import java.io.IOException;
import java.util.Comparator;
import java.io.InputStream;

/**
 * Built using CHelper plug-in
 * Actual solution is at the top
 */
public class Main {
    public static void main(String[] args) {
        InputStream inputStream = System.in;
        OutputStream outputStream = System.out;
        InputReader in = new InputReader(inputStream);
        PrintWriter out = new PrintWriter(outputStream);
        TaskC solver = new TaskC();
        solver.solve(1, in, out);
        out.close();
    }

    static class TaskC {
        private static final int MOD = (int) 1e9 + 7;

        public void solve(int testNumber, InputReader in, PrintWriter out) {
            int n = in.nextInt();

            int[] primes = getPrimes(40_000);

            int[][] a = new int[n][];
            for (int i = 0; i < n; ++i) {
                int x = in.nextInt();
                IntList divs = new IntList();
                for (int j : primes) {
                    if (j * j > x) {
                        break;
                    }
                    int cnt = 0;
                    while (x % j == 0) {
                        cnt++;
                        x /= j;
                    }
                    if (cnt % 2 == 1) {
                        divs.add(j);
                    }
                }
                if (x > 1) {
                    divs.add(x);
                }
                a[i] = divs.toArray();
            }
            Comparator<int[]> cmp = ((o1, o2) -> {
                for (int i = 0; i < o1.length && i < o2.length; ++i) {
                    if (o1[i] < o2[i]) {
                        return -1;
                    } else if (o2[i] < o1[i]) {
                        return 1;
                    }
                }
                return Integer.compare(o1.length, o2.length);
            });
            Arrays.sort(a, cmp);
            IntList freqsList = new IntList();
            int cnt = 1;
            for (int i = 1; i < n; ++i) {
                if (cmp.compare(a[i], a[i - 1]) == 0) {
                    cnt++;
                } else {
                    freqsList.add(cnt);
                    cnt = 1;
                }
            }
            freqsList.add(cnt);
            int[][] comb = new int[2 * n + 1][2 * n + 1];
            for (int i = 0; i < comb.length; ++i) {
                comb[i][0] = 1;
                for (int j = 1; j <= i; ++j) {
                    comb[i][j] = (comb[i - 1][j] + comb[i - 1][j - 1]) % MOD;
                }
            }
            int[] dp = new int[n];
            int[] ndp = new int[n];
            dp[0] = 1;
            int total = 0;
            int ans = 1;
            for (int x : freqsList.toArray()) {
                Arrays.fill(ndp, 0);
                for (int bad = 0; bad < n; ++bad) {
                    if (dp[bad] == 0) {
                        continue;
                    }
                    for (int putSeparately = 1; putSeparately <= x; ++putSeparately) {
                        for (int breakEq = 0; breakEq <= putSeparately; ++breakEq) {
                            int nState = bad + x - putSeparately - breakEq;
                            if (nState < 0 || nState >= n) {
                                continue;
                            }
                            int rem = total + 1 - bad;
                            int notBreak = putSeparately - breakEq;
                            if (breakEq > bad || notBreak > rem) {
                                continue;
                            }
                            int add = (int) ((long) comb[bad][breakEq] * comb[rem][notBreak] % MOD *
                                    comb[x - 1][putSeparately - 1] % MOD * dp[bad] % MOD);
                            ndp[nState] += add;
                            ndp[nState] %= MOD;
                        }
                    }
                }
                total += x;
                int[] aux = dp;
                dp = ndp;
                ndp = aux;
                ans = (int) ((long) ans * fact(x) % MOD);
            }
            ans = (int) ((long) ans * dp[0] % MOD);
            out.println(ans);
        }

        private int fact(int n) {
            int res = 1;
            for (int i = 2; i <= n; ++i) {
                res = (int) ((long) res * i % MOD);
            }
            return res;
        }

        private int[] getPrimes(int n) {
            boolean[] isPrime = new boolean[n + 1];
            Arrays.fill(isPrime, 2, isPrime.length, true);
            for (int i = 2; i * i <= n; ++i) {
                if (isPrime[i]) {
                    for (int j = i * i; j <= n; j += i) {
                        isPrime[j] = false;
                    }
                }
            }
            IntList primes = new IntList();
            for (int i = 2; i <= n; ++i) {
                if (isPrime[i]) {
                    primes.add(i);
                }
            }
            return primes.toArray();
        }

    }

    static class InputReader {
        private InputStream stream;
        private byte[] buf = new byte[1024];
        private int curChar;
        private int numChars;

        public InputReader(InputStream stream) {
            this.stream = stream;
        }

        public int read() {
            if (numChars == -1)
                throw new UnknownError();
            if (curChar >= numChars) {
                curChar = 0;
                try {
                    numChars = stream.read(buf);
                } catch (IOException e) {
                    throw new UnknownError();
                }
                if (numChars <= 0)
                    return -1;
            }
            return buf[curChar++];
        }

        public int nextInt() {
            return Integer.parseInt(next());
        }

        public String next() {
            int c = read();
            while (isSpaceChar(c))
                c = read();
            StringBuffer res = new StringBuffer();
            do {
                res.appendCodePoint(c);
                c = read();
            } while (!isSpaceChar(c));

            return res.toString();
        }

        private boolean isSpaceChar(int c) {
            return c == ' ' || c == '\n' || c == '\r' || c == '\t' || c == -1;
        }

    }

    static interface IntIterator extends Iterator<Integer> {
    }

    static class IntList implements Iterable<Integer> {
        int[] elem;
        int size;

        public IntList() {
            this(0, 0, 1);
        }

        public IntList(int size) {
            this(size, 0, Math.max(1, size));
        }

        public IntList(int size, int value) {
            this(size, value, Math.max(1, size));
        }

        public IntList(int size, int value, int capacity) {
            elem = new int[capacity];
            Arrays.fill(elem, 0, size, value);
            this.size = size;
        }

        private IntList(int... e) {
            elem = e.clone();
            size = e.length;
        }

        public void add(int e) {
            if (size + 1 > elem.length) {
                increaseCapacity();
            }
            elem[size++] = e;
        }

        private void increaseCapacity() {
            changeCapacity(3 * elem.length / 2 + 1);
        }

        private void changeCapacity(int newCapacity) {
            int[] nElem = new int[newCapacity];
            System.arraycopy(elem, 0, nElem, 0, Math.min(elem.length, newCapacity));
            elem = nElem;
        }

        public IntIterator iterator() {
            return new IntIterator() {
                int pos = 0;

                public Integer next() {
                    return IntList.this.elem[pos++];
                }

                public boolean hasNext() {
                    return pos < IntList.this.size;
                }

                public int nextInt() {
                    return IntList.this.elem[pos++];
                }
            };
        }

        public int[] toArray() {
            return Arrays.copyOf(elem, size);
        }

        public int hashCode() {
            int hashCode = 0;
            for (int i = 0; i < size; ++i) {
                hashCode = 31 * hashCode + elem[i];
            }
            return hashCode;
        }

    }
}


import java.io.OutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.List;
import java.util.StringTokenizer;
import java.io.IOException;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.InputStream;
import java.util.ArrayList;

/**
 * Built using CHelper plug-in
 * Actual solution is at the top
 */
public class Main {
    public static void main(String[] args) {
        InputStream inputStream = System.in;
        OutputStream outputStream = System.out;
        FastScanner in = new FastScanner(inputStream);
        PrintWriter out = new PrintWriter(outputStream);
        TaskC solver = new TaskC();
        solver.solve(1, in, out);
        out.close();
    }

    static class TaskC {
        final int MOD = (int) (1e9 + 7);
        long[][] C;
        long[] fact;

        public void solve(int testNumber, FastScanner in, PrintWriter out) {
            int n = in.nextInt();
            precalc(n);
            int[] a = new int[n];
            for (int i = 0; i < n; i++) {
                a[i] = in.nextInt();
                a[i] = removeSquares(a[i]);
            }
            int[] g = getGroupSizes(a);
            long ans = solve(g);
            for (int x : g) {
                ans = ans * fact[x] % MOD;
            }
            out.println(ans);
        }

        private long solve(int[] a) {
            // For a description, see http://petr-mitrichev.blogspot.com/2017/07/a-week7.html
            long[] d = new long[1];
            d[0] = 1;
            int totalPositions = 1;
            for (int x : a) {
                long[] nd = new long[d.length + x + 1];
                for (int s = 0; s < d.length; s++) {
                    if (d[s] == 0) {
                        continue;
                    }
                    for (int m = 1; m <= x; m++) {
                        for (int p = 0; p <= s && p <= m; p++) {
                            long cur = d[s];
                            cur = cur * C[s][p] % MOD;
                            cur = cur * C[totalPositions - s][m - p] % MOD;
                            cur = cur * f(x, m) % MOD;
                            int ns = s + x - m - p;
                            if (ns >= 0 && ns < nd.length) {
                                nd[ns] += cur;
                                if (nd[ns] >= MOD) {
                                    nd[ns] -= MOD;
                                }
                            }
                        }
                    }
                }
                if (totalPositions == 1) {
                    totalPositions = x + 1;
                } else {
                    totalPositions += x;
                }
                d = nd;
            }
            return d[0];
        }

        private long f(int n, int k) {
            if (n < k) {
                return 0;
            }
            n -= k;
            return C[n + k - 1][k - 1];
        }

        private void precalc(int n) {
            fact = new long[n + 1];
            fact[0] = 1;
            for (int i = 1; i < fact.length; i++) {
                fact[i] = i * fact[i - 1] % MOD;
            }

            C = new long[1000][1000];
            C[0][0] = 1;
            for (int i = 1; i < C.length; i++) {
                C[i][0] = 1;
                for (int j = 1; j < C.length; j++) {
                    C[i][j] = C[i - 1][j - 1] + C[i - 1][j];
                    if (C[i][j] >= MOD) {
                        C[i][j] -= MOD;
                    }
                }
            }
        }

        private int[] getGroupSizes(int[] a) {
            Arrays.sort(a);
            List<Integer> res = new ArrayList<>();
            for (int i = 0; i < a.length; ) {
                int j = i;
                while (j < a.length && a[i] == a[j]) {
                    ++j;
                }
                res.add(j - i);
                i = j;
            }
            int[] r = new int[res.size()];
            for (int i = 0; i < r.length; i++) {
                r[i] = res.get(i);
            }
            return r;
        }

        private int removeSquares(int n) {
            int res = 1;
            for (int d = 2; d * d <= n; d++) {
                if (n % d == 0) {
                    int cur = 0;
                    while (n % d == 0) {
                        n /= d;
                        ++cur;
                    }
                    if (cur % 2 == 1) {
                        res *= d;
                    }
                }
            }
            if (n > 1) {
                res *= n;
            }
            return res;
        }

    }

    static class FastScanner {
        private BufferedReader in;
        private StringTokenizer st;

        public FastScanner(InputStream stream) {
            in = new BufferedReader(new InputStreamReader(stream));
        }

        public String next() {
            while (st == null || !st.hasMoreTokens()) {
                try {
                    st = new StringTokenizer(in.readLine());
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
            return st.nextToken();
        }

        public int nextInt() {
            return Integer.parseInt(next());
        }

    }
}


import java.io.OutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.StringTokenizer;
import java.io.IOException;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.InputStream;

/**
 * Built using CHelper plug-in
 * Actual solution is at the top
 */
public class Main {
    public static void main(String[] args) {
        InputStream inputStream = System.in;
        OutputStream outputStream = System.out;
        InputReader in = new InputReader(inputStream);
        PrintWriter out = new PrintWriter(outputStream);
        TaskC solver = new TaskC();
        solver.solve(1, in, out);
        out.close();
    }

    static class TaskC {
        static final int MODULO = (int) (1e9 + 7);

        public void solve(int testNumber, InputReader in, PrintWriter out) {
            int n = in.nextInt();
            int[] a = new int[n];
            for (int i = 0; i < n; ++i) a[i] = removeSquares(in.nextInt());
            Arrays.sort(a);

            int[][] c = new int[n + 1][n + 1];
            c[0][0] = 1;
            for (int i = 1; i < c.length; ++i) {
                c[i][0] = 1;
                for (int j = 1; j < c.length; ++j) {
                    c[i][j] = (c[i - 1][j - 1] + c[i - 1][j]) % MODULO;
                }
            }

            int[] fact = new int[n + 1];
            fact[0] = 1;
            for (int i = 1; i < fact.length; ++i) {
                fact[i] = (int) (i * (long) fact[i - 1] % MODULO);
            }

            int i = 0;
            int[] ways = new int[]{1};

            while (i < n) {
                int j = i;
                while (j < n && a[j] == a[i]) ++j;

                int m = j - i;

                int[] nways = new int[j + 1];
                for (int old = 0; old < ways.length; ++old) {
                    long w = ways[old];
                    for (int blocks = 1; blocks <= m; ++blocks) {
                        for (int intoOld = 0; intoOld <= old && intoOld <= blocks; ++intoOld) {
                            nways[old - intoOld + (m - blocks)] = (int) ((nways[old - intoOld + (m - blocks)] + w * c[old][intoOld] % MODULO * c[i + 1 - old][blocks - intoOld] % MODULO * c[m - 1][blocks - 1] % MODULO * fact[m] % MODULO) % MODULO);
                        }
                    }
                }

                i = j;
                ways = nways;
            }

            out.println(ways[0]);
        }

        private int removeSquares(int x) {
            for (int i = 2; i * i <= x; ++i) {
                while (x % (i * i) == 0) {
                    x /= i * i;
                }
            }
            return x;
        }

    }

    static class InputReader {
        public BufferedReader reader;
        public StringTokenizer tokenizer;

        public InputReader(InputStream stream) {
            reader = new BufferedReader(new InputStreamReader(stream), 32768);
            tokenizer = null;
        }

        public String next() {
            while (tokenizer == null || !tokenizer.hasMoreTokens()) {
                try {
                    tokenizer = new StringTokenizer(reader.readLine());
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
            return tokenizer.nextToken();
        }

        public int nextInt() {
            return Integer.parseInt(next());
        }

    }
}


//package round429;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.InputMismatchException;

public class C2 {
	InputStream is;
	PrintWriter out;
	String INPUT = "";

	void solve()
	{
		int n = ni();
		int[] a = na(n);
		for(int i = 0;i < n;i++){
			int v = a[i];
			for(int j = 2;j*j <= v;j++){
				while(v % (j*j) == 0){
					v /= j*j;
				}
			}
			a[i] = v;
		}

		Arrays.sort(a);
		int[] f = new int[n];
		int p = 0;
		for(int i= 0;i < n;i++){
			if(i > 0 && a[i] != a[i-1]){
				p++;
			}
			f[p]++;
		}
		f = Arrays.copyOf(f, p+1);
		int mod = 1000000007;

		int[][] fif = enumFIF(1000, mod);
		long[] res = countSameNeighborsSequence(f, fif, mod);
		long ans = res[0];
		for(int v : f){
			ans = ans * fif[0][v] % mod;
		}

		out.println(ans);
	}

	public static int[][] enumFIF(int n, int mod) {
		int[] f = new int[n + 1];
		int[] invf = new int[n + 1];
		f[0] = 1;
		for (int i = 1; i <= n; i++) {
			f[i] = (int) ((long) f[i - 1] * i % mod);
		}
		long a = f[n];
		long b = mod;
		long p = 1, q = 0;
		while (b > 0) {
			long c = a / b;
			long d;
			d = a;
			a = b;
			b = d % b;
			d = p;
			p = q;
			q = d - c * q;
		}
		invf[n] = (int) (p < 0 ? p + mod : p);
		for (int i = n - 1; i >= 0; i--) {
			invf[i] = (int) ((long) invf[i + 1] * (i + 1) % mod);
		}
		return new int[][] { f, invf };
	}

	public static long[] countSameNeighborsSequence(int[] a, int[][] fif, int mod)
	{
		int n = a.length;

		int bef = a[0];
		int aft = a[0];
		long[] dp = new long[bef];
		dp[bef-1] = 1;
		for(int u = 1;u < n;u++){
			int v = a[u];
			aft += v;
			long[][] ldp = new long[bef][aft];
			for(int i = 0;i < dp.length;i++){
				ldp[i][0] = dp[i];
			}

			for(int i = 0;i < v;i++){
				long[][] ndp = new long[bef][aft];
				for(int j = 0;j < bef;j++){
					for(int k = 0;j+k < aft;k++){
						if(ldp[j][k] == 0)continue;
						// XX -> XCX
						if(j > 0){
							ndp[j-1][k] += ldp[j][k] * j;
							ndp[j-1][k] %= mod;
						}

						// CC -> CCC
						if(k > 0){
							ndp[j][k+1] += ldp[j][k] * k;
							ndp[j][k+1] %= mod;
						}

						// XC -> XCC
						// #XC = 2*(i-k)
						if(2*(i-k) > 0){
							ndp[j][k+1] += ldp[j][k] * (2*(i-k));
							ndp[j][k+1] %= mod;
						}

						// XY -> XCY
						// #XY = bef+i+1-#XC-#CC-#XX
						if(bef+i+1-j-k-2*(i-k) > 0){
							ndp[j][k] += ldp[j][k] * (bef+i+1-j-k-2*(i-k));
							ndp[j][k] %= mod;
						}
					}
				}
				ldp = ndp;
			}

			dp = new long[aft];
			for(int j = 0;j < bef;j++){
				for(int k = 0;j+k < aft;k++){
					dp[j+k] += ldp[j][k];
					if(dp[j+k] >= mod)dp[j+k] -= mod;
				}
			}
			for(int j = 0;j < aft;j++)dp[j] = dp[j] * fif[1][v] % mod;
			bef = aft;
		}
		return dp;
	}

	void run() throws Exception
	{
		is = oj ? System.in : new ByteArrayInputStream(INPUT.getBytes());
		out = new PrintWriter(System.out);

		long s = System.currentTimeMillis();
		solve();
		out.flush();
		tr(System.currentTimeMillis()-s+"ms");
	}

	public static void main(String[] args) throws Exception { new C2().run(); }

	private byte[] inbuf = new byte[1024];
	public int lenbuf = 0, ptrbuf = 0;

	private int readByte()
	{
		if(lenbuf == -1)throw new InputMismatchException();
		if(ptrbuf >= lenbuf){
			ptrbuf = 0;
			try { lenbuf = is.read(inbuf); } catch (IOException e) { throw new InputMismatchException(); }
			if(lenbuf <= 0)return -1;
		}
		return inbuf[ptrbuf++];
	}

	private boolean isSpaceChar(int c) { return !(c >= 33 && c <= 126); }
	private int skip() { int b; while((b = readByte()) != -1 && isSpaceChar(b)); return b; }

	private double nd() { return Double.parseDouble(ns()); }
	private char nc() { return (char)skip(); }

	private String ns()
	{
		int b = skip();
		StringBuilder sb = new StringBuilder();
		while(!(isSpaceChar(b))){ // when nextLine, (isSpaceChar(b) && b != ' ')
			sb.appendCodePoint(b);
			b = readByte();
		}
		return sb.toString();
	}

	private char[] ns(int n)
	{
		char[] buf = new char[n];
		int b = skip(), p = 0;
		while(p < n && !(isSpaceChar(b))){
			buf[p++] = (char)b;
			b = readByte();
		}
		return n == p ? buf : Arrays.copyOf(buf, p);
	}

	private char[][] nm(int n, int m)
	{
		char[][] map = new char[n][];
		for(int i = 0;i < n;i++)map[i] = ns(m);
		return map;
	}

	private int[] na(int n)
	{
		int[] a = new int[n];
		for(int i = 0;i < n;i++)a[i] = ni();
		return a;
	}

	private int ni()
	{
		int num = 0, b;
		boolean minus = false;
		while((b = readByte()) != -1 && !((b >= '0' && b <= '9') || b == '-'));
		if(b == '-'){
			minus = true;
			b = readByte();
		}

		while(true){
			if(b >= '0' && b <= '9'){
				num = num * 10 + (b - '0');
			}else{
				return minus ? -num : num;
			}
			b = readByte();
		}
	}

	private long nl()
	{
		long num = 0;
		int b;
		boolean minus = false;
		while((b = readByte()) != -1 && !((b >= '0' && b <= '9') || b == '-'));
		if(b == '-'){
			minus = true;
			b = readByte();
		}

		while(true){
			if(b >= '0' && b <= '9'){
				num = num * 10 + (b - '0');
			}else{
				return minus ? -num : num;
			}
			b = readByte();
		}
	}

	private boolean oj = System.getProperty("ONLINE_JUDGE") != null;
	private void tr(Object... o) { if(!oj)System.out.println(Arrays.deepToString(o)); }
}


import java.io.*;
import java.util.*;
import java.math.*;
import java.util.concurrent.*;

public final class on_the_bench
{
    static BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
	static FastScanner sc=new FastScanner(br);
    static PrintWriter out=new PrintWriter(System.out);
	static Random rnd=new Random();
	static int[] parent,size;
	static int maxn=(int)500;
	static long mod=(long)(1e9+7);
	static int[] fact,inv_fact;

	static int getParent(int u)
	{
		if(u==parent[u])
		{
			return u;
		}

		else
		{
			int val=getParent(parent[u]);parent[u]=val;

			return val;
		}
	}

	static void merge(int u,int v)
	{
		int x=getParent(u),y=getParent(v);

		if(x!=y)
		{
			parent[y]=x;

			size[x]+=size[y];size[y]=0;
		}
	}

	static int add(long a,long b)
	{
		long ret=a+b;

		if(ret>=mod)
		{
			ret%=mod;
		}

		return (int)ret;
	}

	static int mul(long a,long b)
	{
		long ret=a*b;

		if(ret>=mod)
		{
			ret%=mod;
		}

		return (int)ret;
	}

	static int pow(long a,long b)
	{
		long x=1,y=a;

		while(b>0)
		{
			if(b%2==1)
			{
				x=mul(x,y);
			}

			y=mul(y,y);b=b/2;
		}

		return (int)(x%mod);
	}

	static void build()
	{
		fact=new int[maxn];inv_fact=new int[maxn];fact[0]=1;

		for(int i=1;i<maxn;i++)
		{
			fact[i]=mul(fact[i-1],i);
		}

		inv_fact[maxn-1]=pow(fact[maxn-1],mod-2);

		for(int i=maxn-2;i>=0;i--)
		{
			inv_fact[i]=mul(inv_fact[i+1],(i+1));
		}
	}

	static int[] mul_poly(int[] a,int[] b,int deg1,int deg2)
	{
		int[] ret=new int[deg1+deg2+1];

		for(int i=0;i<=deg1;i++)
		{
			for(int j=0;j<=deg2;j++)
			{
				int curr=mul(a[i],b[j]);

				ret[i+j]=add(ret[i+j],curr);
			}
		}

		return ret;
	}

	static int C(int n,int r)
	{
		if(n-r<0 || Math.min(n,r)<0)
		{
			return 0;
		}

		int val1=fact[n],val2=inv_fact[r],val3=inv_fact[n-r];

		int mul=mul(val2,val3);

		return mul(val1,mul);
	}

    public static void main(String args[]) throws Exception
    {
		int n=sc.nextInt();build();

		int[] a=new int[n];parent=new int[n];size=new int[n];

		for(int i=0;i<n;i++)
		{
			a[i]=sc.nextInt();

			parent[i]=i;

			size[i]=1;
		}

		for(int i=0;i<n;i++)
		{
			for(int j=i+1;j<n;j++)
			{
				long curr=a[i]*1L*a[j],now=(long)Math.sqrt(curr);

				if(now*now==curr)
				{
					merge(i,j);
				}
			}
		}

		List<Integer> list=new ArrayList<>();

		for(int i=0;i<n;i++)
		{
			if(getParent(i)==i)
			{
				list.add(size[i]);
			}
		}

	//	out.println(list);

		int res=0;int[] poly=new int[1];poly[0]=1;

		for(int i=0;i<list.size();i++)
		{
			int size=list.get(i);

			int[] arr=new int[size];arr[0]=1;

			for(int j=1;j<size;j++)
			{
				int now1=C(size,j),now2=mul(fact[size-1],inv_fact[size-1-j]);

				int qq=mul(now1,now2);

				arr[j]=qq;
			}

			poly=mul_poly(poly,arr,poly.length-1,size-1);
		}

		for(int i=1,x=1;i<poly.length;i++,x*=-1)
		{
			int now=add(x,mod);

			int curr=mul(fact[n-i],poly[i]);

			curr=mul(curr,now);

			res=add(res,curr);
		}

	//	out.println(res);

		int zz=mul(res,mod-1);res=add(fact[n],zz);

		out.println(res);out.close();
    }
}
class FastScanner
{
    BufferedReader in;
    StringTokenizer st;

    public FastScanner(BufferedReader in) {
        this.in = in;
    }

    public String nextToken() throws Exception {
        while (st == null || !st.hasMoreTokens()) {
            st = new StringTokenizer(in.readLine());
        }
        return st.nextToken();
    }

	public String next() throws Exception {
		return nextToken().toString();
	}

    public int nextInt() throws Exception {
        return Integer.parseInt(nextToken());
    }

    public long nextLong() throws Exception {
        return Long.parseLong(nextToken());
    }

    public double nextDouble() throws Exception {
        return Double.parseDouble(nextToken());
    }
}


import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.HashMap;
import java.util.StringTokenizer;
/*

2
3 5

2
6 5
1 11
-10 12

3
3 6 2

1   8   5   3
-7  9   5   3
-7  4   14  3

1   8   5   3
1   3   13  3
-2  4   13  3

3
1 3 6

3 3 6

5 12
-7 19

3 5 7 11 13
-2 8

5 9

5 9

3 6 7
-3 9 7

6 3 7

*/

public class c {
	static int n;
	static int[] fs;
	static int[] cfs;
	static long[][] choose = chooseTable(1001);
	static long[] fact;
	static final long MOD = (long) (1e9 + 7);
	static long[][] memo;
	public static void main(String[] args) throws IOException {
		FastScanner in = new FastScanner(System.in);
		n = in.nextInt();
		fact = new long[301];
		fact[0] = 1;
		for (int i = 1; i < fact.length; i++) {
			fact[i] = fact[i - 1] * i;
			fact[i] %= MOD;
		}
		HashMap<Long, Integer> map = new HashMap<Long, Integer>();
		fs = new int[n];
		for (int i = 0; i < n; i++) {
			long v = in.nextLong();
			long r = 1;
			for (int d = 2; d * d <= v; d++) {
				int cnt = 0;
				while (v % d == 0) {
					v /= d;
					cnt ^= 1;
				}
				if (cnt == 1) {
					r *= d;
				}
			}
			r *= v;
			if (!map.containsKey(r)) {
				map.put(r, map.size());
			}
			fs[map.get(r)]++;
		}
		cfs = new int[n];
		for (int i = 1; i < n; i++) {
			cfs[i] = cfs[i - 1] + fs[i - 1];
		}
		memo = new long[n+1][n+1];
		for(long[] arr : memo)
			Arrays.fill(arr, -1);
		System.out.println(go(0, 0));
	}

	static long go(int color, int priorities) {
		if (color == n)
			return priorities == 0 ? 1 : 0;
//		System.out.println(color + " "+  priorities);
		if(memo[color][priorities] != -1)
			return memo[color][priorities];
		int nonpriorities = cfs[color] - priorities + 1;
		long ans = 0;
		for (int cntPrio = 0; cntPrio <= priorities && cntPrio <= fs[color]; cntPrio++) {
			for (int cntNonPrio = 0; cntNonPrio <= nonpriorities && cntNonPrio + cntPrio <= fs[color]; cntNonPrio++) {
				if(cntPrio + cntNonPrio == 0 && fs[color] != 0) continue;
				int cntExtra = fs[color] - cntPrio - cntNonPrio;
				long tmp = choose(priorities, cntPrio);
				tmp *= choose(nonpriorities, cntNonPrio);
				tmp %= MOD;
				tmp *= multichoose(cntPrio + cntNonPrio, cntExtra);
				tmp %= MOD;
				tmp *= go(color + 1, priorities - cntPrio + cntExtra);
				tmp %= MOD;
				tmp *= fact[fs[color]];
				tmp %= MOD;

				ans += tmp;
				ans %= MOD;
			}
		}
		return memo[color][priorities] = ans;
	}

	static long[][] chooseTable(int n) {
		long[][] table = new long[n][];
		for (int x = 0; x < n; x++) {
			table[x] = new long[x + 1];
			table[x][0] = table[x][x] = 1;
			for (int y = 1; y < x; y++) {
				table[x][y] = table[x - 1][y - 1] + table[x - 1][y];
				table[x][y] %= MOD;
			}
		}
		return table;
	}

	static long choose(int n, int k) {
		if (k == 0)
			return 1;
		if (k >= choose[n].length) {
			return 0;
		}
		return choose[n][k];
	}

	static long multichoose(int n, int k) {
		return choose(n + k - 1, k);
	}

	static class FastScanner {
		BufferedReader br;
		StringTokenizer st;

		public FastScanner(InputStream i) {
			br = new BufferedReader(new InputStreamReader(i));
		}

		public String next() throws IOException {
			while (st == null || !st.hasMoreTokens())
				st = new StringTokenizer(br.readLine());
			return st.nextToken();
		}

		public int nextInt() throws IOException {
			return Integer.parseInt(next());
		}

		public long nextLong() throws IOException {
			return Long.parseLong(next());
		}

		public double nextDouble() throws IOException {
			return Double.parseDouble(next());
		}
	}
}


//package round429;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.InputMismatchException;

public class C2 {
	InputStream is;
	PrintWriter out;
	String INPUT = "";

	void solve()
	{
		int n = ni();
		int[] a = na(n);
		for(int i = 0;i < n;i++){
			int v = a[i];
			for(int j = 2;j*j <= v;j++){
				while(v % (j*j) == 0){
					v /= j*j;
				}
			}
			a[i] = v;
		}

		Arrays.sort(a);
		int[] f = new int[n];
		int p = 0;
		for(int i= 0;i < n;i++){
			if(i > 0 && a[i] != a[i-1]){
				p++;
			}
			f[p]++;
		}
		f = Arrays.copyOf(f, p+1);
		int mod = 1000000007;

		int[][] fif = enumFIF(1000, mod);
		long[] res = countSameNeighborsSequence(f, fif, mod);
		long ans = res[0];
		for(int v : f){
			ans = ans * fif[0][v] % mod;
		}

		out.println(ans);
	}

	public static int[][] enumFIF(int n, int mod) {
		int[] f = new int[n + 1];
		int[] invf = new int[n + 1];
		f[0] = 1;
		for (int i = 1; i <= n; i++) {
			f[i] = (int) ((long) f[i - 1] * i % mod);
		}
		long a = f[n];
		long b = mod;
		long p = 1, q = 0;
		while (b > 0) {
			long c = a / b;
			long d;
			d = a;
			a = b;
			b = d % b;
			d = p;
			p = q;
			q = d - c * q;
		}
		invf[n] = (int) (p < 0 ? p + mod : p);
		for (int i = n - 1; i >= 0; i--) {
			invf[i] = (int) ((long) invf[i + 1] * (i + 1) % mod);
		}
		return new int[][] { f, invf };
	}

	public static long[] countSameNeighborsSequence(int[] a, int[][] fif, int mod)
	{
		int n = a.length;

		int bef = a[0];
		int aft = a[0];
		long[] dp = new long[bef];
		dp[bef-1] = 1;
		for(int u = 1;u < n;u++){
			int v = a[u];
			aft += v;
			long[][] ldp = new long[bef][aft];
			for(int i = 0;i < dp.length;i++){
				ldp[i][0] = dp[i];
			}

			for(int i = 0;i < v;i++){
				long[][] ndp = new long[bef][aft];
				for(int j = 0;j < bef;j++){
					for(int k = 0;j+k < aft;k++){
						if(ldp[j][k] == 0)continue;
						// XX -> XCX
						if(j > 0){
							ndp[j-1][k] += ldp[j][k] * j;
							ndp[j-1][k] %= mod;
						}

						// CC -> CCC
						// XC -> XCC
						// #XC = 2*(i-k)
						ndp[j][k+1] += ldp[j][k] * (2*i-k);
						ndp[j][k+1] %= mod;

						// XY -> XCY
						// #XY = bef+i+1-#XC-#CC-#XX
						ndp[j][k] += ldp[j][k] * (bef+i+1-j-k-2*(i-k));
						ndp[j][k] %= mod;
					}
				}
				ldp = ndp;
			}

			dp = new long[aft];
			for(int j = 0;j < bef;j++){
				for(int k = 0;j+k < aft;k++){
					dp[j+k] += ldp[j][k];
					if(dp[j+k] >= mod)dp[j+k] -= mod;
				}
			}
			for(int j = 0;j < aft;j++)dp[j] = dp[j] * fif[1][v] % mod;
			bef = aft;
		}
		return dp;
	}

	void run() throws Exception
	{
		is = oj ? System.in : new ByteArrayInputStream(INPUT.getBytes());
		out = new PrintWriter(System.out);

		long s = System.currentTimeMillis();
		solve();
		out.flush();
		tr(System.currentTimeMillis()-s+"ms");
	}

	public static void main(String[] args) throws Exception { new C2().run(); }

	private byte[] inbuf = new byte[1024];
	public int lenbuf = 0, ptrbuf = 0;

	private int readByte()
	{
		if(lenbuf == -1)throw new InputMismatchException();
		if(ptrbuf >= lenbuf){
			ptrbuf = 0;
			try { lenbuf = is.read(inbuf); } catch (IOException e) { throw new InputMismatchException(); }
			if(lenbuf <= 0)return -1;
		}
		return inbuf[ptrbuf++];
	}

	private boolean isSpaceChar(int c) { return !(c >= 33 && c <= 126); }
	private int skip() { int b; while((b = readByte()) != -1 && isSpaceChar(b)); return b; }

	private double nd() { return Double.parseDouble(ns()); }
	private char nc() { return (char)skip(); }

	private String ns()
	{
		int b = skip();
		StringBuilder sb = new StringBuilder();
		while(!(isSpaceChar(b))){ // when nextLine, (isSpaceChar(b) && b != ' ')
			sb.appendCodePoint(b);
			b = readByte();
		}
		return sb.toString();
	}

	private char[] ns(int n)
	{
		char[] buf = new char[n];
		int b = skip(), p = 0;
		while(p < n && !(isSpaceChar(b))){
			buf[p++] = (char)b;
			b = readByte();
		}
		return n == p ? buf : Arrays.copyOf(buf, p);
	}

	private char[][] nm(int n, int m)
	{
		char[][] map = new char[n][];
		for(int i = 0;i < n;i++)map[i] = ns(m);
		return map;
	}

	private int[] na(int n)
	{
		int[] a = new int[n];
		for(int i = 0;i < n;i++)a[i] = ni();
		return a;
	}

	private int ni()
	{
		int num = 0, b;
		boolean minus = false;
		while((b = readByte()) != -1 && !((b >= '0' && b <= '9') || b == '-'));
		if(b == '-'){
			minus = true;
			b = readByte();
		}

		while(true){
			if(b >= '0' && b <= '9'){
				num = num * 10 + (b - '0');
			}else{
				return minus ? -num : num;
			}
			b = readByte();
		}
	}

	private long nl()
	{
		long num = 0;
		int b;
		boolean minus = false;
		while((b = readByte()) != -1 && !((b >= '0' && b <= '9') || b == '-'));
		if(b == '-'){
			minus = true;
			b = readByte();
		}

		while(true){
			if(b >= '0' && b <= '9'){
				num = num * 10 + (b - '0');
			}else{
				return minus ? -num : num;
			}
			b = readByte();
		}
	}

	private boolean oj = System.getProperty("ONLINE_JUDGE") != null;
	private void tr(Object... o) { if(!oj)System.out.println(Arrays.deepToString(o)); }
}


//package round429;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.InputMismatchException;

public class C2 {
	InputStream is;
	PrintWriter out;
	String INPUT = "";

	void solve()
	{
		int n = ni();
		int[] a = na(n);
		for(int i = 0;i < n;i++){
			int v = a[i];
			for(int j = 2;j*j <= v;j++){
				while(v % (j*j) == 0){
					v /= j*j;
				}
			}
			a[i] = v;
		}

		Arrays.sort(a);
		int[] f = new int[n];
		int p = 0;
		for(int i= 0;i < n;i++){
			if(i > 0 && a[i] != a[i-1]){
				p++;
			}
			f[p]++;
		}
		f = Arrays.copyOf(f, p+1);
		int mod = 1000000007;

		int[][] fif = enumFIF(1000, mod);
		long[] res = countSameNeighborsSequence(f, fif, mod);
		long ans = res[0];
		for(int v : f){
			ans = ans * fif[0][v] % mod;
		}

		out.println(ans);
	}

	public static int[][] enumFIF(int n, int mod) {
		int[] f = new int[n + 1];
		int[] invf = new int[n + 1];
		f[0] = 1;
		for (int i = 1; i <= n; i++) {
			f[i] = (int) ((long) f[i - 1] * i % mod);
		}
		long a = f[n];
		long b = mod;
		long p = 1, q = 0;
		while (b > 0) {
			long c = a / b;
			long d;
			d = a;
			a = b;
			b = d % b;
			d = p;
			p = q;
			q = d - c * q;
		}
		invf[n] = (int) (p < 0 ? p + mod : p);
		for (int i = n - 1; i >= 0; i--) {
			invf[i] = (int) ((long) invf[i + 1] * (i + 1) % mod);
		}
		return new int[][] { f, invf };
	}

	public static long[] countSameNeighborsSequence(int[] a, int[][] fif, int mod)
	{
		int n = a.length;

		int bef = a[0];
		int aft = a[0];
		long[] dp = new long[bef];
		dp[bef-1] = 1;
		for(int u = 1;u < n;u++){
			int v = a[u];
			aft += v;
			long[][] ldp = new long[bef][aft];
			for(int i = 0;i < dp.length;i++){
				ldp[i][0] = dp[i];
			}

			for(int i = 0;i < v;i++){
				long[][] ndp = new long[bef][aft];
				for(int j = 0;j < bef;j++){
					for(int k = 0;j+k < aft;k++){
						if(ldp[j][k] == 0)continue;
						// XX -> XCX
						if(j > 0){
							ndp[j-1][k] += ldp[j][k] * j;
							ndp[j-1][k] %= mod;
						}

						// CC -> CCC
						// XC -> XCC
						// #XC = 2*(i-k)
						ndp[j][k+1] += ldp[j][k] * (2*i-k);
						ndp[j][k+1] %= mod;

						// XY -> XCY
						// #XY = bef+i+1-#XC-#CC-#XX
						ndp[j][k] += ldp[j][k] * (bef+i+1-j-k-2*(i-k));
						ndp[j][k] %= mod;
					}
				}
				ldp = ndp;
			}

			dp = new long[aft];
			for(int j = 0;j < bef;j++){
				for(int k = 0;j+k < aft;k++){
					dp[j+k] += ldp[j][k];
				}
			}
			for(int j = 0;j < aft;j++)dp[j] = dp[j] % mod * fif[1][v] % mod;
			bef = aft;
		}
		return dp;
	}

	void run() throws Exception
	{
		is = oj ? System.in : new ByteArrayInputStream(INPUT.getBytes());
		out = new PrintWriter(System.out);

		long s = System.currentTimeMillis();
		solve();
		out.flush();
		tr(System.currentTimeMillis()-s+"ms");
	}

	public static void main(String[] args) throws Exception { new C2().run(); }

	private byte[] inbuf = new byte[1024];
	public int lenbuf = 0, ptrbuf = 0;

	private int readByte()
	{
		if(lenbuf == -1)throw new InputMismatchException();
		if(ptrbuf >= lenbuf){
			ptrbuf = 0;
			try { lenbuf = is.read(inbuf); } catch (IOException e) { throw new InputMismatchException(); }
			if(lenbuf <= 0)return -1;
		}
		return inbuf[ptrbuf++];
	}

	private boolean isSpaceChar(int c) { return !(c >= 33 && c <= 126); }
	private int skip() { int b; while((b = readByte()) != -1 && isSpaceChar(b)); return b; }

	private double nd() { return Double.parseDouble(ns()); }
	private char nc() { return (char)skip(); }

	private String ns()
	{
		int b = skip();
		StringBuilder sb = new StringBuilder();
		while(!(isSpaceChar(b))){ // when nextLine, (isSpaceChar(b) && b != ' ')
			sb.appendCodePoint(b);
			b = readByte();
		}
		return sb.toString();
	}

	private char[] ns(int n)
	{
		char[] buf = new char[n];
		int b = skip(), p = 0;
		while(p < n && !(isSpaceChar(b))){
			buf[p++] = (char)b;
			b = readByte();
		}
		return n == p ? buf : Arrays.copyOf(buf, p);
	}

	private char[][] nm(int n, int m)
	{
		char[][] map = new char[n][];
		for(int i = 0;i < n;i++)map[i] = ns(m);
		return map;
	}

	private int[] na(int n)
	{
		int[] a = new int[n];
		for(int i = 0;i < n;i++)a[i] = ni();
		return a;
	}

	private int ni()
	{
		int num = 0, b;
		boolean minus = false;
		while((b = readByte()) != -1 && !((b >= '0' && b <= '9') || b == '-'));
		if(b == '-'){
			minus = true;
			b = readByte();
		}

		while(true){
			if(b >= '0' && b <= '9'){
				num = num * 10 + (b - '0');
			}else{
				return minus ? -num : num;
			}
			b = readByte();
		}
	}

	private long nl()
	{
		long num = 0;
		int b;
		boolean minus = false;
		while((b = readByte()) != -1 && !((b >= '0' && b <= '9') || b == '-'));
		if(b == '-'){
			minus = true;
			b = readByte();
		}

		while(true){
			if(b >= '0' && b <= '9'){
				num = num * 10 + (b - '0');
			}else{
				return minus ? -num : num;
			}
			b = readByte();
		}
	}

	private boolean oj = System.getProperty("ONLINE_JUDGE") != null;
	private void tr(Object... o) { if(!oj)System.out.println(Arrays.deepToString(o)); }
}


import java.io.*;
import java.util.*;

public class MainD {
	static final StdIn in = new StdIn();
	static final PrintWriter out = new PrintWriter(System.out);
	static final long M=(long)1e9+7;
	static long[] fac, faci;

	public static void main(String[] args) {
		int n=in.nextInt();
		fac = new long[n+1];
		faci = new long[n+1];
		fac[0]=faci[0]=1;
		for(int i=1; i<=n; ++i)
			faci[i]=modI(fac[i]=fac[i-1]*i%M, M);
		List<List<Integer>> grps = new ArrayList<List<Integer>>();
		for(int i=0; i<n; ++i) {
			int ai=in.nextInt();
			for(int j=0; ; ++j) {
				if(j>=grps.size())
					grps.add(new ArrayList<Integer>());
				if(grps.get(j).size()>0&&!ps((long)grps.get(j).get(0)*ai))
					continue;
				grps.get(j).add(ai);
				break;
			}
		}
		long[][] dp = new long[grps.size()][n-grps.size()+1];
		dp[0][grps.get(0).size()-1]=fac[grps.get(0).size()];
		int ad=grps.get(0).size();
		for(int i=1; i<grps.size(); ++i) {
			for(int j=0; j<dp[i-1].length; ++j) {
				if(dp[i-1][j]==0)
					continue;
				for(int k=0; k<grps.get(i).size(); ++k)
					for(int l=Math.max(grps.get(i).size()+j-k-ad-1, 0); l<=Math.min(grps.get(i).size()-k, j); ++l)
						dp[i][j+k-l]=(fac[grps.get(i).size()]*nck(j, l)%M*nck(ad+1-j, grps.get(i).size()-k-l)%M*nck(grps.get(i).size()-1, k)%M*dp[i-1][j]+dp[i][j+k-l])%M;
			}
			ad+=grps.get(i).size();
		}
		//out.println(Arrays.deepToString(dp));
		out.println(dp[grps.size()-1][0]);
		out.close();
	}

	static long modI(long a, long m) {
		return (a%=m)<=1?1:((1-modI(m%a, a)*m)/a+m)%m;
	}

	static long nck(int n, int k) {
		return fac[n]*faci[k]%M*faci[n-k]%M;
	}

	static boolean ps(long x) {
		long lb=1, rb=M;
		while(lb<=rb) {
			long mb=(lb+rb)/2;
			if(mb*mb==x)
				return true;
			if(mb*mb<x)
				lb=mb+1;
			else
				rb=mb-1;
		}
		return false;
	}

	static class StdIn {
		final private int BUFFER_SIZE = 1 << 16;
		private DataInputStream din;
		private byte[] buffer;
		private int bufferPointer, bytesRead;
		public StdIn() {
			din = new DataInputStream(System.in);
			buffer = new byte[BUFFER_SIZE];
			bufferPointer = bytesRead = 0;
		}
		public StdIn(InputStream in) {
			try{
				din = new DataInputStream(in);
			} catch(Exception e) {
				throw new RuntimeException();
			}
			buffer = new byte[BUFFER_SIZE];
			bufferPointer = bytesRead = 0;
		}
		public String next() {
			int c;
			while((c=read())!=-1&&(c==' '||c=='\n'||c=='\r'));
			StringBuilder s = new StringBuilder();
			while (c != -1)
			{
				if (c == ' ' || c == '\n'||c=='\r')
					break;
				s.append((char)c);
				c=read();
			}
			return s.toString();
		}
		public String nextLine() {
			int c;
			while((c=read())!=-1&&(c==' '||c=='\n'||c=='\r'));
			StringBuilder s = new StringBuilder();
			while (c != -1)
			{
				if (c == '\n'||c=='\r')
					break;
				s.append((char)c);
				c = read();
			}
			return s.toString();
		}
		public int nextInt() {
			int ret = 0;
			byte c = read();
			while (c <= ' ')
				c = read();
			boolean neg = (c == '-');
			if (neg)
				c = read();
			do
				ret = ret * 10 + c - '0';
			while ((c = read()) >= '0' && c <= '9');

			if (neg)
				return -ret;
			return ret;
		}
		public int[] readIntArray(int n, int os) {
			int[] ar = new int[n];
			for(int i=0; i<n; ++i)
				ar[i]=nextInt()+os;
			return ar;
		}
		public long nextLong() {
			long ret = 0;
			byte c = read();
			while (c <= ' ')
				c = read();
			boolean neg = (c == '-');
			if (neg)
				c = read();
			do
				ret = ret * 10 + c - '0';
			while ((c = read()) >= '0' && c <= '9');
			if (neg)
				return -ret;
			return ret;
		}
		public long[] readLongArray(int n, long os) {
			long[] ar = new long[n];
			for(int i=0; i<n; ++i)
				ar[i]=nextLong()+os;
			return ar;
		}
		public double nextDouble() {
			double ret = 0, div = 1;
			byte c = read();
			while (c <= ' ')
				c = read();
			boolean neg = (c == '-');
			if (neg)
				c = read();
			do
				ret = ret * 10 + c - '0';
			while ((c = read()) >= '0' && c <= '9');
			if (c == '.')
				while ((c = read()) >= '0' && c <= '9')
					ret += (c - '0') / (div *= 10);
			if (neg)
				return -ret;
			return ret;
		}
		private void fillBuffer() throws IOException {
			bytesRead = din.read(buffer, bufferPointer = 0, BUFFER_SIZE);
			if (bytesRead == -1)
				buffer[0] = -1;
		}
		private byte read() {
			try{
				if (bufferPointer == bytesRead)
					fillBuffer();
				return buffer[bufferPointer++];
			} catch(IOException e) {
				throw new RuntimeException();
			}
		}
		public void close() throws IOException {
			if (din == null)
				return;
			din.close();
		}
	}
}


// Author : RegalBeast

import java.io.*;
import java.util.*;

public class Main {
  static final FastReader FR = new FastReader();
  static final PrintWriter PW = new PrintWriter(new OutputStreamWriter(System.out));

  public static void main(String[] args) {
    StringBuilder solution = new StringBuilder();
    int rows = FR.nextInt();
    int cols = FR.nextInt();
    int moves = FR.nextInt();

    Map<Integer, Integer> horizontalEdgeWeights = new HashMap<Integer, Integer>();
    for (int r = 0; r < rows; r++) {
      for (int c = 0; c < cols - 1; c++) {
        int hash = getHash(r, c);
        horizontalEdgeWeights.put(hash, FR.nextInt());
      }
    }

    Map<Integer, Integer> verticalEdgeWeights = new HashMap<Integer, Integer>();
    for (int r = 0; r < rows - 1; r++) {
      for (int c = 0; c < cols; c++) {
        int hash = getHash(r, c);
        verticalEdgeWeights.put(hash, FR.nextInt());
      }
    }

    List<List<Integer>> result = getResult(rows, cols, moves, horizontalEdgeWeights, verticalEdgeWeights);
    for (int r = 0; r < rows; r++) {
      for (int c = 0; c < cols; c++) {
        int value = (result != null ? result.get(r).get(c) : -1);
        solution.append(value + " ");
      }
      solution.append("\n");
    }

		PW.print(solution.toString());
    PW.close();
  }

  static List<List<Integer>> getResult(int rows, int cols, int moves, Map<Integer, Integer> horizontalEdgeWeights, Map<Integer, Integer> verticalEdgeWeights) {
    if ((moves & 1) == 1) {
      return null;
    }

    int mid = moves >> 1;
    List<List<List<Integer>>> minForDistance = new ArrayList<List<List<Integer>>>(rows);
    for (int r = 0; r < rows; r++) {
      minForDistance.add(new ArrayList<List<Integer>>(cols));

      for (int c = 0; c < cols; c++) {
        minForDistance.get(r).add(new ArrayList<Integer>(Collections.nCopies(mid+1, Integer.MAX_VALUE)));
        minForDistance.get(r).get(c).set(0, 0);
      }
    }

    for (int m = 1; m <= mid; m++) {
      for (int r = 0; r < rows; r++) {
        for (int c = 0; c < cols; c++) {
          int minBoredom = minForDistance.get(r).get(c).get(m);
          int hash = getHash(r, c);

          if (r > 0) {
            if (minForDistance.get(r-1).get(c).get(m-1) < Integer.MAX_VALUE) {
              int candidateBoredom = minForDistance.get(r-1).get(c).get(m-1) + verticalEdgeWeights.get(getHash(r-1, c));
              minBoredom = Math.min(minBoredom, candidateBoredom);
            }
          }

          if (c > 0) {
            if (minForDistance.get(r).get(c-1).get(m-1) < Integer.MAX_VALUE) {
              int candidateBoredom = minForDistance.get(r).get(c-1).get(m-1) + horizontalEdgeWeights.get(getHash(r, c-1));
              minBoredom = Math.min(minBoredom, candidateBoredom);
            }
          }

          if (r + 1 < rows) {
            if (minForDistance.get(r+1).get(c).get(m-1) < Integer.MAX_VALUE) {
              int candidateBoredom = minForDistance.get(r+1).get(c).get(m-1) + verticalEdgeWeights.get(hash);
              minBoredom = Math.min(minBoredom, candidateBoredom);
            }
          }

          if (c + 1 < cols) {
            if (minForDistance.get(r).get(c+1).get(m-1) < Integer.MAX_VALUE) {
              int candidateBoredom = minForDistance.get(r).get(c+1).get(m-1) + horizontalEdgeWeights.get(hash);
              minBoredom = Math.min(minBoredom, candidateBoredom);
            }
          }

          minForDistance.get(r).get(c).set(m, minBoredom);
        }
      }
    }

    List<List<Integer>> result = new ArrayList<List<Integer>>(rows);
    for (int r = 0; r < rows; r++) {
      result.add(new ArrayList<Integer>(cols));

      for (int c = 0; c < cols; c++) {
        result.get(r).add(minForDistance.get(r).get(c).get(mid) << 1);
      }
    }

    return result;
  }

  static int getHash(int row, int col) {
    return (row * 1000 + col);
  }
  static int getRow(int hash) {
    return hash / 1000;
  }
  static int getCol(int hash) {
    return hash % 1000;
  }

  static class FastReader {
    BufferedReader br;
    StringTokenizer st;

    public FastReader() {
      br = new BufferedReader(new InputStreamReader(System.in));
    }

    String next() {
      while (st == null || !st.hasMoreElements()) {
        try {
          st = new StringTokenizer(br.readLine());
        } catch (IOException  e) {
          e.printStackTrace();
        }
      }
      return st.nextToken();
    }

    int nextInt() {
      return Integer.parseInt(next());
    }

    long nextLong() {
      return Long.parseLong(next());
    }

    double nextDouble() {
      return Double.parseDouble(next());
    }

    String nextLine() {
      String str = "";
      try {
        str = br.readLine();
      } catch (IOException e)  {
        e.printStackTrace();
      }
      return str;
    }
  }
}


import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.*;

import static java.lang.Double.parseDouble;
import static java.lang.Integer.parseInt;
import static java.lang.Long.parseLong;
import static java.lang.System.in;
import static java.lang.System.out;

public class SolutionD extends Thread {
    static class FastReader {
        BufferedReader br;
        StringTokenizer st;

        public FastReader() {
            br = new BufferedReader(new
                                            InputStreamReader(in));
        }

        String next() {
            while (st == null || !st.hasMoreElements()) {
                try {
                    st = new StringTokenizer(br.readLine());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return st.nextToken();
        }

        int nextInt() {
            return parseInt(next());
        }

        long nextLong() {
            return parseLong(next());
        }

        double nextDouble() {
            return parseDouble(next());
        }

        String nextLine() {
            String str = "";
            try {
                str = br.readLine();
            } catch (IOException e) {
                e.printStackTrace();
            }
            return str;
        }
    }

    private static final FastReader scanner = new FastReader();
    private static final PrintWriter out = new PrintWriter(System.out);

    public static void main(String[] args) {
        solve();
        out.close();
    }

    private static void solve() {
        int n = scanner.nextInt();
        int m = scanner.nextInt();
        int k = scanner.nextInt();

        int[][] hori = new int[n][m-1];
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m-1; j++) {
                int xij = scanner.nextInt();
                hori[i][j] = xij;
            }
        }

        int[][] vert = new int[n-1][m];
        for (int i = 0; i < n-1; i++) {
            for (int j = 0; j < m; j++) {
                int xij = scanner.nextInt();
                vert[i][j] = xij;
            }
        }

        if (k % 2 != 0) {
            for (int i = 0; i < n; i++) {
                StringBuilder s = new StringBuilder();
                for (int j = 0; j < m; j++) {
                    s.append("-1 ");
                }
                out.println(s);
            }
            return;
        }

        k /= 2;

        long[][][] dp = new long[n][m][k+1];

        for (int kTmp = 1; kTmp <= k; kTmp++) {
            for (int i = 0; i < n; i++) {
                for (int j = 0; j < m; j++) {
                    dp[i][j][kTmp] = Integer.MAX_VALUE;
                    if (i > 0) {
                        dp[i][j][kTmp] = Math.min(dp[i][j][kTmp], dp[i-1][j][kTmp-1] + 2L * vert[i - 1][j]);
                    }
                    if (j > 0) {
                        dp[i][j][kTmp] = Math.min(dp[i][j][kTmp], dp[i][j-1][kTmp-1] + 2L * hori[i][j - 1]);
                    }
                    if (i + 1 < n) {
                        dp[i][j][kTmp] = Math.min(dp[i][j][kTmp], dp[i+1][j][kTmp-1] + 2L * vert[i][j]);
                    }
                    if (j + 1 < m) {
                        dp[i][j][kTmp] = Math.min(dp[i][j][kTmp], dp[i][j+1][kTmp-1] + 2L * hori[i][j]);
                    }
                }
            }
        }

        for (int i = 0; i < n; i++) {
            StringBuilder s = new StringBuilder();
            for (int j = 0; j < m; j++) {
                s.append(dp[i][j][k]).append(" ");
            }
            out.println(s);
        }
    }
}