Problem:
Score : 600 points

Problem Statement

You are given a forest with N vertices and M edges. The vertices are numbered 0 through N-1.
The edges are given in the format (x_i,y_i), which means that Vertex x_i and y_i are connected by an edge.

Each vertex i has a value a_i.
You want to add edges in the given forest so that the forest becomes connected.
To add an edge, you choose two different vertices i and j, then span an edge between i and j.
This operation costs a_i + a_j dollars, and afterward neither Vertex i nor j can be selected again.

Find the minimum total cost required to make the forest connected, or print Impossible if it is impossible.

Constraints

1 ≤ N ≤ 100,000

0 ≤ M ≤ N-1

1 ≤ a_i ≤ 10^9

0 ≤ x_i,y_i ≤ N-1

The given graph is a forest.

All input values are integers.

Input

Input is given from Standard Input in the following format:

N M
a_0 a_1 .. a_{N-1}
x_1 y_1
x_2 y_2
:
x_M y_M

Output

Print the minimum total cost required to make the forest connected, or print Impossible if it is impossible.

Sample Input 1

7 5
1 2 3 4 5 6 7
3 0
4 0
1 2
1 3
5 6

Sample Output 1

7

If we connect vertices 0 and 5, the graph becomes connected, for the cost of 1 + 6 = 7 dollars.

Sample Input 2

5 0
3 1 4 1 5

Sample Output 2

Impossible

We can't make the graph connected.

Sample Input 3

1 0
5

Sample Output 3

0

The graph is already connected, so we do not need to add any edges.

Solution in Java (OpenJDK 11.0.6) (judge verdict: Runtime Error)


import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.PriorityQueue;
import java.util.StringTokenizer;
import java.util.function.BiFunction;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class Main {
    public static void main(final String[] args) {
        final FastScanner scanner = new FastScanner(System.in);
        final int n = scanner.nextInt();
        final int m = scanner.nextInt();

        final BiFunction<Integer, Integer, Boolean> isTree = (nodes, edges) -> edges == nodes - 1;
        if (isTree.apply(n, m)) {
            System.out.println(0);
            return;
        }

        final List<Long> costs = IntStream.range(0, n)
            .mapToObj(i -> scanner.nextLong())
            .collect(Collectors.toList());

        final UnionFindTree unionFindTree = new UnionFindTree(n);
        for (int i = 0; i < m; i++) {
            unionFindTree.unit(scanner.nextInt(), scanner.nextInt());
        }

        final List<Tree> trees = IntStream.range(0, n)
            .boxed()
            .collect(Collectors.groupingBy(unionFindTree::getRoot))
            .entrySet().stream()
            .map(entry -> new Tree(entry.getKey(), entry.getValue().stream().map(costs::get).collect(Collectors.toList())))
            .collect(Collectors.toList());

        // 木の数をKとすると，張らなければならない辺の数はK-1本
        // L本の辺を張るのに2*L個の頂点が必要
        // なので 2*(K - 1) <= N でない時辺を張れない
        if (2 * (trees.size() - 1) > n) {
            System.out.println("Impossible");
            return;
        }

        final PriorityQueue<Tree> queue = new PriorityQueue<>(Comparator.comparingLong(tree -> tree.queue.peek()));
        queue.addAll(trees);
        final UnionFindTree unionFindTree2 = new UnionFindTree(n);
        long sum = 0;
        for (int i = 0; i < trees.size() - 1; i++) {
            final Tree poll = queue.poll();
            final List<Tree> tmp = new ArrayList<>();
            while (true) {
                final Tree current = queue.poll();
                if (!unionFindTree2.isSame(poll.id, current.id)) {
                    sum += poll.queue.poll() + current.queue.poll();
                    break;
                } else {
                    tmp.add(current);
                }
            }

            queue.addAll(tmp);
            if (!poll.queue.isEmpty()) {
                queue.add(poll);
            }
        }

        System.out.println(sum);
    }

    private static class Tree {
        final int id;
        final PriorityQueue<Long> queue;

        Tree(final int id, final List<Long> list) {
            this.id = id;
            this.queue = new PriorityQueue<>(list);
        }
    }

    private static class UnionFindTree {
        private final Integer[] nodes;
        private final List<Integer> indices = new ArrayList<>();

        UnionFindTree(final int numOfNodes) {
            this.nodes = init(numOfNodes);
        }

        private Integer[] init(final int numOfNodes) {
            return IntStream.range(0, numOfNodes)
                .boxed()
                .toArray(Integer[]::new);
        }

        /**
         * 引数のノードが属している木の根を返す．
         *
         * @param nodeNumber ノードの番号
         * @return 根，つまり属している集合の中の一番小さい値
         */
        int getRoot(final int nodeNumber) {
            final int rootNode = nodes[nodeNumber];
            if (rootNode == nodeNumber) {
                for (final Integer index : indices) {
                    nodes[index] = rootNode;
                }
                indices.clear();
                return nodeNumber;
            }

            indices.add(nodeNumber);
            return getRoot(rootNode);
        }

        /**
         * 二つのノードが同じ集合に属しているかを判定する．
         *
         * @param nodeA ノード
         * @param nodeB ノード
         * @return 二つのノードが同じ集合に属しているかの判定結果
         */
        boolean isSame(final int nodeA, final int nodeB) {
            return getRoot(nodeA) == getRoot(nodeB);
        }

        /**
         * 引数のノードが属する集合を合体させる．
         *
         * @param nodeA ノード
         * @param nodeB ノード
         */
        void unit(final int nodeA, final int nodeB) {
            final int rootA = getRoot(nodeA);
            final int rootB = getRoot(nodeB);

            if (rootA == rootB) {
                return;
            }

            nodes[Math.max(rootA, rootB)] = Math.min(rootA, rootB);
        }
    }

    private static class FastScanner {
        private final BufferedReader reader;
        private StringTokenizer tokenizer;

        FastScanner(final InputStream in) {
            reader = new BufferedReader(new InputStreamReader(in));
        }

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

        String nextLine() {
            if (tokenizer == null || !tokenizer.hasMoreTokens()) {
                try {
                    return reader.readLine();
                } catch (final IOException e) {
                    throw new RuntimeException(e);
                }
            }
            return tokenizer.nextToken("\n");
        }

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

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

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