Problem:
Score : 600 points

Problem Statement

We have N strings of lowercase English letters: S_1, S_2, \cdots, S_N.

Takahashi wants to make a string that is a palindrome by choosing one or more of these strings - the same string can be chosen more than once - and concatenating them in some order of his choice.

The cost of using the string S_i once is C_i, and the cost of using it multiple times is C_i multiplied by that number of times.

Find the minimum total cost needed to choose strings so that Takahashi can make a palindrome.

If there is no choice of strings in which he can make a palindrome, print -1.

Constraints

1 \leq N \leq 50

1 \leq |S_i| \leq 20

S_i consists of lowercase English letters.

1 \leq C_i \leq 10^9

Input

Input is given from Standard Input in the following format:

N
S_1 C_1
S_2 C_2
:
S_N C_N

Output

Print the minimum total cost needed to choose strings so that Takahashi can make a palindrome, or -1 if there is no such choice.

Sample Input 1

3
ba 3
abc 4
cbaa 5

Sample Output 1

7

We have ba, abc, and cbaa.

For example, we can use ba once and abc once for a cost of 7, then concatenate them in the order abc, ba to make a palindrome.
Also, we can use abc once and cbaa once for a cost of 9, then concatenate them in the order cbaa, abc to make a palindrome.

We cannot make a palindrome for a cost less than 7, so we should print 7.

Sample Input 2

2
abcab 5
cba 3

Sample Output 2

11

We can choose abcab once and cba twice, then concatenate them in the order abcab, cba, cba to make a palindrome for a cost of 11.

Sample Input 3

4
ab 5
cba 3
a 12
ab 10

Sample Output 3

8

We can choose a once, which is already a palindrome, but it is cheaper to concatenate ab and cba.

Sample Input 4

2
abc 1
ab 2

Sample Output 4

-1

We cannot make a palindrome, so we should print -1.

Solution in Java (OpenJDK 11.0.6) (judge verdict: Wrong Answer)

import java.io.IOException;
import java.io.InputStream;
import java.util.*;

public class Main {
	//入力値
	int      N;
	String[] S;
	long[]   C;
	
	long minCost = Long.MAX_VALUE;
	
	class Element implements Comparable<Element>{
		String left, right;
		long cost = 0L;
		
		public Element(int index){
			left  = S[index];
			right = "";
			cost  = C[index];
		}
		
		public Element(int left, int right) {
			this.left  = S[left];
			this.right = S[right];
			this.cost  = C[left] + C[right];
		}
		
		public Element(String left, String right, long cost) {
			this.left  = left;
			this.right = right;
			this.cost  = cost;
		}
		
		@Override
		public int compareTo(Element o) {
			return (int)( (cost - o.cost) % 1000000007L );
		}
	}
	
	public Main() {
		FastScanner scanner = new FastScanner();
		N = scanner.nextInt();
		S = new String[N];
		C = new long[N];
		
		for (int i = 0; i < N; i++) {
			S[i] = scanner.next();
			C[i] = scanner.nextLong();
		}
		
		TreeSet<Element> palindromeSet = new TreeSet<>();
		for (int l = 0; l < N; l++) {
			if (isPalindrome(S[l]))
				minCost = Math.min(minCost, C[l]);
			
			for (int r = 0; r < N; r++) {
				if (l == r) continue;
				palindromeSet.add( new Element(l,r) );
			}
		}
		
		while( !palindromeSet.isEmpty() ){
			Element elem = palindromeSet.pollFirst();
			String L  = elem.left;
			String R  = elem.right;
			long cost = elem.cost;
			
			if (minCost < cost){
				System.out.println(minCost);
				return;
			}
			
			int left = 0, right = R.length()-1;
			boolean isContinue = false;
			
			while(true){
				// 回文にならない
				if (L.charAt(left) != R.charAt(right)){
					isContinue = true;
					break;
				}
				
				if (left == L.length()-1 || right == 0){
					L = L.substring(left + 1);
					R = R.substring(0, right);
					break;
				}
				
				left++;
				right--;
			}
			
			if (isContinue) continue;
			
			if (L.isEmpty()){
				//すでに回文である
				if (isPalindrome(R)) {
					System.out.println(cost);
					return;
				}
				
				String reverse = new StringBuffer(R).reverse().toString();
				for (int i = 0; i < N; i++) {
					if ( S[i].startsWith(reverse) ) {
						palindromeSet.add( new Element(S[i], R, cost + C[i]) );
					}
				}
				
			} else {
				//終了条件：すでに回文である
				if (isPalindrome(L)) {
					System.out.println(cost);
					return;
				}
				
				String reverse = new StringBuffer(L).reverse().toString();
				for (int i = 0; i < N; i++) {
					if ( S[i].endsWith(reverse) ) {
						palindromeSet.add( new Element(L, S[i], cost + C[i]) );
					}
				}
			}
			
		}
		
		System.out.println(-1);
	}
	
	/*
	void solve(String L, String R, long costSum){
		int left = 0, right = R.length() - 1;
		
		while(true){
			//終了条件：回文ではない
			if (L.charAt(left) != R.charAt(right)) return;
			
			if (left == L.length() - 1 || right == 0){
				L = L.substring(left + 1);
				R = R.substring(0, right);
				break;
			}
			
			left++;
			right--;
		}
		
		if (L.isEmpty()){
			//終了条件：すでに回文である
			if (isPalindrome(R)) {
				minCost = Math.min(minCost, costSum);
				return;
			}
			
			String reverse = new StringBuffer(R).reverse().toString();
			for (int i = 0; i < N; i++) {
				//costが減ることは無いのでcostの最小値を超えた時点で枝かり
				if (minCost <= costSum + C[i]) continue;
				
				if ( S[i].startsWith(reverse) ) {
					solve(S[i], R, costSum + C[i]);
				}
			}
		} else {
			//終了条件：すでに回文である
			if (isPalindrome(L)) {
				minCost = Math.min(minCost, costSum);
				return;
			}
			
			String reverse = new StringBuffer(L).reverse().toString();
			for (int i = 0; i < N; i++) {
				//costが減ることは無いのでcostの最小値を超えた時点で枝かり
				if (minCost <= costSum + C[i]) continue;
				
				if ( S[i].endsWith(reverse) ) {
					solve(L, S[i], costSum + C[i]);
				}
			}
		}
	}
	 */
	
	// 文字列sが回文かどうかを判定する．
	// 0文字はtrue
	boolean isPalindrome(String s){
		int len = s.length();
		
		for (int i = 0; 2*i + 1 < len; i++) {
			if (s.charAt(i) != s.charAt(len - i - 1))
				return false;
		}
		
		return true;
	}
	
	public static void main(String[] args) {
		new Main();
	}
}

class FastScanner {
	private final InputStream in = System.in;
	private final byte[] buffer = new byte[1024];
	private int ptr = 0;
	private int buflen = 0;
	
	private boolean hasNextByte() {
		if (ptr < buflen) {
			return true;
		} else {
			ptr = 0;
			try {
				buflen = in.read(buffer);
			} catch (IOException e) {
				e.printStackTrace();
			}
			if (buflen <= 0) {
				return false;
			}
		}
		return true;
	}
	
	private int readByte() {
		if (hasNextByte()) return buffer[ptr++];
		else return -1;
	}
	
	private static boolean isPrintableChar(int c) {
		return 33 <= c && c <= 126;
	}
	
	public boolean hasNext() {
		while (hasNextByte() && !isPrintableChar(buffer[ptr])) ptr++;
		return hasNextByte();
	}
	
	public String next() {
		if (!hasNext()) throw new NoSuchElementException();
		StringBuilder sb = new StringBuilder();
		int b = readByte();
		while (isPrintableChar(b)) {
			sb.appendCodePoint(b);
			b = readByte();
		}
		return sb.toString();
	}
	
	public long nextLong() {
		if (!hasNext()) throw new NoSuchElementException();
		long n = 0;
		boolean minus = false;
		int b = readByte();
		if (b == '-') {
			minus = true;
			b = readByte();
		}
		if (b < '0' || '9' < b) {
			throw new NumberFormatException();
		}
		while (true) {
			if ('0' <= b && b <= '9') {
				n *= 10;
				n += b - '0';
			} else if (b == -1 || !isPrintableChar(b)) {
				return minus ? -n : n;
			} else {
				throw new NumberFormatException();
			}
			b = readByte();
		}
	}
	
	public int nextInt() {
		long nl = nextLong();
		if (nl < Integer.MIN_VALUE || nl > Integer.MAX_VALUE) throw new NumberFormatException();
		return (int) nl;
	}
	
	public double nextDouble() {
		return Double.parseDouble(next());
	}
}