// Arup Guha
// Started on 8/31/2014, finished on 9/4/2014
// Solution to 2014 UCF Locals Contest Problem: UCF (Under Construction Forever)

import java.util.*;
import java.io.*;

public class ucf {

	// Constants used.
	final public static long MOD = 1000000007L;
	final public static int MAX = 500;
	final public static int NONE = -1;

	// For factorials and mod inverses of factorials for counting.
	public static long[] fact;
	public static long[] factinv;

	// To store original graph.
	public static int n;
	public static int e;
	public static int[] cost;
	public static ArrayList[] graph;
	public static boolean[] isOrigLeaf;

	// To store underlying structural tree for counting.
	public static boolean[] isRoot;
	public static boolean[] used;
	public static boolean[] removed;
	public static ArrayList[] origNeighbors;
	public static ArrayList[] neighborFlip;

	public static void main(String[] args) throws Exception {

		// Fill in factorial and inverse tables for later
		// computation.
		fact = new long[MAX+1];
		factinv = new long[MAX+1];
		fact[0] = 1;
		factinv[0] = 1;
		for (int i=1; i<=MAX; i++)
			fact[i] = (fact[i-1]*i)%MOD;
		for (int i=1; i<=MAX; i++)
			factinv[i] = modInv(fact[i], MOD);

		// Read input file to process cases.
		Scanner stdin = new Scanner(new File("ucf.in"));
		int numCases = stdin.nextInt();

		for (int loop=1; loop<=numCases; loop++) {

			// Read in graph basics + cost.
			n = stdin.nextInt();
			e = stdin.nextInt();
			cost = new int[n];
			for (int i=0; i<n; i++)
				cost[i] = stdin.nextInt();

			// Form graph data struct.
			graph = new ArrayList[n];
			for (int i=0; i<n; i++)
				graph[i] = new ArrayList<Integer>();

			// Read in edges.
			for (int i=0; i<e; i++) {
				int v1 = stdin.nextInt() - 1;
				int v2 = stdin.nextInt() - 1;
				graph[v1].add(v2);
				graph[v2].add(v1);
			}

			// Remember original "leaves" in graph.
			isOrigLeaf = new boolean[n];
			isRoot = new boolean[n];
			Arrays.fill(isRoot, true);
			for (int i=0; i<n; i++) {
				if (graph[i].size() == 1) {
					isOrigLeaf[i] = true;
					isRoot[i] = false;
				}
			}

			// Super-annoying special case.
			if (n == 2) {
				if (cost[0] == cost[1]) System.out.println("Case #"+loop+": "+1+" "+cost[0]+" "+2);
				else					System.out.println("Case #"+loop+": "+1+" "+Math.min(cost[0],cost[1])+" "+1);
				continue;
			}

			// Simulate to obtain tree structure.
			sim();

			// Use tree structure to solve counting problem.
			long[] sol = getNumWays(-1, origNeighbors);
			long numWays = sol[1];

			// Count up buildings left.
			int buildings = 0;
			for (int i=0; i<n; i++)
				if (!removed[i])
					buildings++;

			// Add up total cost.
			int totalCost = 0;
			for (int i=0; i<n; i++)
				if (used[i])
					totalCost += cost[i];

			// Tree case - last building can be anything in subgraph.
			if (e == n-1) {

				// Find the unique root of this underlying tree.
				int root = -1;
				for (int i=0; i<n; i++)
					if (isRoot[i])
						root = i;

				// Store reverse edges
				getRev();

				// Try each vertex as the root.
				for (int i=0; i<n; i++) {

					// A root we haven't previously used.
					if (origNeighbors[i] != null && !isRoot[i]) {
						ArrayList[] curNeighbors = getNewTree(i, root);

						isRoot[root] = false;
						isRoot[i] = true;

						long[] tmp = getNumWays(i, curNeighbors);
						numWays = (numWays + tmp[1])%MOD;

						isRoot[root] = true;
						isRoot[i] = false;
					}
				}
			}

			// Final answer.
			System.out.println("Case #"+loop+": "+buildings+" "+totalCost+" "+numWays);
		}
		stdin.close();
	}

	// Takes a tree rooted at oldRoot and produces a new tree rooted at newRoot with the
	// same undirected graph structure.
	public static ArrayList[] getNewTree(int newRoot, int oldRoot) {

		ArrayList[] ans = new ArrayList[n];
		Arrays.fill(ans, null);
		HashSet<Integer> used = new HashSet<Integer>();

		// Edges to flip...from newRoot up to oldRoot.
		while (newRoot != oldRoot) {
			int next = (Integer)neighborFlip[newRoot].get(0);
			ans[newRoot] = new ArrayList<Integer>();
			ans[newRoot].add(next);
			used.add(1000*newRoot+next);
			newRoot = next;
		}

		// Add in all edges that haven't been added yet, in original orientation.
		for (int i=0; i<n; i++) {
			if (origNeighbors[i] != null) {

				for (int j=0; j<origNeighbors[i].size(); j++) {

					int next =(Integer)origNeighbors[i].get(j);
					if (!used.contains(1000*next+i)) {
						if (ans[i] == null) ans[i] = new ArrayList<Integer>();
						if (ans[next] == null) ans[next] = new ArrayList<Integer>();
						ans[i].add(next);
					}
				}
			}
		}
		return ans;
	}

	// Calculates the graph of flipping everything in the neighbor graph.
	public static void getRev() {

		// Structure to store the "flip" graph.
		neighborFlip = new ArrayList[n];
		Arrays.fill(neighborFlip, null);

		for (int i=0; i<n; i++) {
			if (origNeighbors[i] != null) {

				// Must not be null, so check.
				if (neighborFlip[i] == null) neighborFlip[i] = new ArrayList<Integer>();

				// Add the reverse edge.
				for (int j=0; j<origNeighbors[i].size(); j++) {
					int next = (Integer)origNeighbors[i].get(j);
					if (neighborFlip[next] == null) neighborFlip[next] = new ArrayList<Integer>();
					neighborFlip[next].add(i);
				}
			}
		}
	}

	// Returns the number of the nodes in the tree rooted at root as well as the number of
	// ways to restructure when we reduce to that node.
	public static long[] getNumWays(int root, ArrayList[] neighbors) {

		// Non-existent node in neighbor structure.
		if (root != -1 && neighbors[root] == null) {
			long[] tmp = new long[2];
			tmp[0] = 0; tmp[1] = 1;
			return tmp;
		}

		// Base case: leaf node.
		if (root != -1 && neighbors[root].size() == 0) {
			long[] tmp = new long[2];
			tmp[0] = 1; tmp[1] = 1;
			return tmp;
		}

		// Store answers here.
		long[] ans = new long[2];
		ans[0] = 0;
		ans[1] = 1;
		ArrayList<Integer> sizes = new ArrayList<Integer>();

		// Evaluate for whole graph.
		if (root == -1) {

			// Evaluate all independent branches.
			for (int i=0; i<n; i++) {
				if (isRoot[i]) {
					long[] tmp = getNumWays(i, neighbors);
					sizes.add((int)tmp[0]);
					ans[0] += tmp[0];
					ans[1] = (ans[1]*tmp[1])%MOD;
				}
			}
		}
		else {

			// Solve each branch of this root.
			for (int i=0; i<neighbors[root].size(); i++) {
				long[] tmp = getNumWays((Integer)neighbors[root].get(i), neighbors);
				sizes.add((int)tmp[0]);
				ans[0] += tmp[0];
				ans[1] = (ans[1]*tmp[1])%MOD;
			}
		}

		// Multiply numerator by factorial of all nodes.
		ans[1] = (ans[1]*fact[(int)ans[0]])%MOD;

		// Divide by factorials of all subtrees.
		for (int i=0; i<sizes.size(); i++)
			ans[1] = (ans[1]*factinv[(int)sizes.get(i)])%MOD;

		// Correction for rooted tree.
		if (root != -1) ans[0]++;
		return ans;
	}

	// Simulate the process of restructuring, from the outside nodes in.
	public static void sim() {

		// Set up underlying graph structure.
		origNeighbors = new ArrayList[n];
		for (int i=0; i<n; i++)
			origNeighbors[i] = null;
		removed = new boolean[n];
		used = new boolean[n];

		// Allows us to go outside in.
		PriorityQueue<Integer> pqLeaf = new PriorityQueue<Integer>();
		for (int i=0; i<n; i++)
			if (isOrigLeaf[i])
				pqLeaf.offer(1000*(500-cost[i]) + i);

		// Simulate until there are no more leaves to cut.
		while (pqLeaf.size() > 0) {

			// Extract next item.
			int next = pqLeaf.poll();
			int level = next/1000000;
			int node = next%1000;

			// Woo hoo, we are done!
			if (graph[node].size() == 0) break;

			// Get the parent of this node.
			int par = (Integer)graph[node].get(0);

			// Must create this link since it's part of our neighbor graph.
			if (origNeighbors[par] == null) origNeighbors[par] = new ArrayList<Integer>();

			// Add to key underlying graph structure, if necessary.
			if (!isOrigLeaf[node]) {
				origNeighbors[par].add(node);
				isRoot[node] = false;
			}

			// Remove this node.
			removed[node] = true;
			used[par] = true;
			graph[node].remove(0);
			for (int i=0; i<graph[par].size(); i++) {
				if (  ((Integer)(graph[par].get(i))).equals(node)) {
					graph[par].remove(i);
					break;
				}
			}

			// See if removing the edge made this a new leaf node.
			if (graph[par].size() == 1)
				pqLeaf.offer(1000000*(level+1)+1000*(500-cost[par]) + par);
		}

		// We don't want these!
		for (int i=0; i<n; i++)
			if (origNeighbors[i] == null)
				isRoot[i] = false;
	}

	// Basic O(n) method to find a "leaf", if one exists.
	public static int getLeaf() {
		for (int i=0; i<n; i++)
			if (graph[i].size() == 1)
				return i;
		return -1;
	}

	public static long modExp(long b, long e) {
		if (e == 0) return 1L;
		if (e%2 == 0) {
			long sqrt = modExp(b, e/2);
			return (sqrt*sqrt)%MOD;
		}
		return (b*modExp(b,e-1))%MOD;
	}

	public static long modInv(long a, long b) {
		return modExp(a, b-2);
	}
}