// Arup Guha
// 3/16/2018
// Solution to 2018 Mercer Problem 11: Schrodinger's Character.

import java.util.*;

public class schrodinger {

	public static int n;
	public static String[] words;
	public static long numChars;
	public static long mod;
	public static node trie;

	public static void main(String[] args) {

		Scanner stdin = new Scanner(System.in);
		int nC = stdin.nextInt();

		// Process each case.
		for (int loop=0; loop<nC; loop++) {

			// Set up Trie.
			node.setTotal();
			trie = new node();

			// Get words and insert into trie.
			n = stdin.nextInt();
			words = new String[n];
			for (int i=0; i<n; i++) {
				words[i] = stdin.next();
				trie.insert(words[i], 0);
			}

			// Get query numbers
			numChars = stdin.nextLong();
			mod = stdin.nextLong();

			// Ta da!
			System.out.println(useSch()+" "+regCase());
		}
	}

	// Solves the case where we get to use the Schrodinger character.
	public static long useSch() {

		long res = 0;

		// Add in contribution of each word separately.
		for (String s: words) {
			int len = s.length();

			// This is for when the word is first.
			if (numChars >= len)
				res = (res + modExp(27, numChars-len))%mod;

			// Otherwise, we place the @ before the word in any location. (So we can start with a newline...)
			// Notice the mod inside the first set of parens. Without this we could overflow long since
			// numChars could be 10^12 and the modExp could return something around 10^9.
			if (numChars-len-1 >= 0)
				res = res + (((numChars-len+mod)%mod)*modExp(27,numChars-len-1))%mod;
			res = res%mod;
		}

		return res;
	}

	// Returns base ^ exp mod mod (static var).
	public static long modExp(long base, long exp) {
		if (exp == 0L) return 1L;
		if (exp%2 == 0L) {
			long tmp = modExp(base, exp/2);
			return (tmp*tmp)%mod;
		}
		return (base*modExp(base,exp-1))%mod;
	}

	// Solves the regular case without the Schodinger character.
	public static long regCase() {

		// Get our matrix.
		long[][] mat = trie.getMat();

		// When we raise the matrix to the first power, we get paths of length 1, so we should
		// just raise it to the numChars power.
		long[][] res = repeat(mat, numChars);

		// Find the largest answer on top row. Our best path could end up at any node in the trie.
		long ans = res[0][0];
		for (int i=0; i<res[0].length; i++)
			ans = Math.max(ans, res[0][i]);

		return ans;
	}

	// Repeats traveling through matrix a exp times.
	public static long[][] repeat(long[][] a, long exp) {

		// Easy case.
		if (exp == 1) return a;

		// Savings here, divide by 2 and then combine.
		if (exp%2 == 0) {
			long[][] tmp = repeat(a, exp/2);
			return combine(tmp, tmp);
		}

		// Slow case, just peel off one copy of a.
		long[][] tmp = repeat(a, exp-1);
		return combine(tmp, a);
	}

	// Assumes a and b are same size and square.
	// This doesn't do a real matrix multiply. The matrix multiply tries different paths.
	// We don't want the sum of paths, but the maximum points along any one path.
	public static long[][] combine(long[][] a, long[][] b) {

		int len = a.length;
		long[][] res = new long[len][len];
		for (int i=0; i<len; i++) Arrays.fill(res[i], -1);

		// Logic here is that a[i][k] stores points on this path i->k and b k->j stores points
		// on that path so adding them stores all the points on that one path. Of all the possible
		// paths, we want the one that adds to the points points on it.
		for (int i=0; i<len; i++)
			for (int j=0; j<len; j++)
				for (int k=0; k<len; k++)

					// This if makes sure we don't take a fake path with a -1.
					if (a[i][k] >= 0 && b[k][j] >= 0)
						res[i][j] = Math.max(res[i][j], a[i][k] + b[k][j]);

		return res;
	}
}

class node {

	public static int total;

	public int cnt;
	public node[] next;
	public int ID;

	public node() {
		cnt = 0;
		next = new node[26];
		Arrays.fill(next, null);
		ID = total++;
	}

	public static void setTotal() {
		total = 0;
	}

	public void insert(String s, int k) {

		// Got to end; add one to string count.
		if (k == s.length()) {
			cnt += 1;
			return;
		}

		// If where we need to go doesn't exist, make it.
		if (next[s.charAt(k)-'A'] == null)
			next[s.charAt(k)-'A'] = new node();

		// Now, recursively insert.
		next[s.charAt(k)-'A'].insert(s, k+1);
	}

	public long[][] getMat() {

		// -1 indicates no path.
		long[][] res = new long[total][total];
		for (int i=0; i<total; i++) Arrays.fill(res[i], -1);

		LinkedList<node> q = new LinkedList<node>();
		q.offer(this);

		// Run a BFS through the whole trie.
		while (q.size() > 0) {

			node cur = q.poll();

			// Go to each next letter - in each slot we put the number of POINTS (completed words)
			// for going to that state.
			for (int i=0; i<26; i++) {
				if (cur.next[i] != null) {
					q.offer(cur.next[i]);
					res[cur.ID][cur.next[i].ID] = cur.next[i].cnt;
				}
			}

			// We can go back from any node back to the start with at symbol.
			res[cur.ID][this.ID] = 0;
		}

		return res;
	}
}