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 Rust (1.42.0) (judge verdict: Wrong Answer)

use std::cmp::{min, Reverse};
use std::collections::{BTreeMap, BinaryHeap};

const INF: i64 = 1 << 60;

fn naive(t: &[(Vec<char>, i64)], depth: usize, mut buf: &mut Vec<char>, cost: i64) -> i64 {
    if depth == 10 {
        return INF;
    }
    if buf.len() > 0 && is_palindrome(buf) {
        return cost;
    }

    let mut ans = INF;
    for (s, c) in t.iter().map(|x| (&x.0, x.1)) {
        let size = buf.len();
        buf.extend_from_slice(s);
        ans = min(ans, naive(t, depth + 1, buf, cost + c));
        buf.resize(size, ' ');
    }
    ans
}

fn main() {
    let (r, w) = (std::io::stdin(), std::io::stdout());
    let mut sc = IO::new(r.lock(), w.lock());

    // let mut rng = thread_rng();
    // for i in 1.. {
    //     let n = 3;
    //     let m = 10;
    //     let t = (0..n)
    //         .map(|_| {
    //             let s = (0..m)
    //                 .map(|_| {
    //                     (rng.sample(rand::distributions::Uniform::from(0u8..m)) + 'a' as u8) as char
    //                 })
    //                 .collect::<Vec<_>>();
    //             let cost: i64 = rng.sample(rand::distributions::Uniform::from(1..100000));
    //             (s, cost)
    //         })
    //         .collect::<Vec<_>>();
    //     let naive = naive(&t, 0, &mut vec![], 0);
    //     if naive == INF {
    //         assert_eq!(-1, solve(&t), "{:?}", t);
    //     } else {
    //         assert_eq!(naive, solve(&t), "{:?}", t);
    //     }
    //
    //     if i % 100 == 0 {
    //         println!("{}", i);
    //     }
    // }

    let n: usize = sc.read();
    let mut t = vec![];
    for _ in 0..n {
        let s = sc.chars();
        let c: i64 = sc.read();
        t.push((s, c));
    }

    println!("{}", solve(&t));
}

fn solve(t: &[(Vec<char>, i64)]) -> i64 {
    let mut graph = BTreeMap::new();
    for s in t.iter() {
        let s = &s.0;
        let n = s.len();
        for from in 0..n {
            for to in (from + 1)..=n {
                let sub = s[from..to].to_vec();
                graph.insert(sub, Vec::new());
            }
        }
    }

    let v = graph.keys().cloned().collect::<Vec<_>>();
    for from in v.into_iter() {
        for (reversed, cost) in t
            .iter()
            .map(|s| (s.0.clone().into_iter().rev().collect::<Vec<_>>(), s.1))
        {
            let prefix_length = prefix_length(&from, &reversed);

            if prefix_length == from.len() && prefix_length == reversed.len() {
                let to = vec![];
                graph.get_mut(&from).unwrap().push((cost, to));
            } else if prefix_length == from.len() {
                let mut to = reversed[prefix_length..].to_vec();
                to.reverse();
                graph.get_mut(&from).unwrap().push((cost, to));
            } else if prefix_length == reversed.len() {
                let to = from[prefix_length..].to_vec();
                graph.get_mut(&from).unwrap().push((cost, to));
            }
        }
    }

    let mut heap = BinaryHeap::new();
    let mut dist = BTreeMap::new();
    for (s, cost) in t.iter() {
        let cur = dist.entry(s.clone()).or_insert(INF);
        if *cur > *cost {
            *cur = *cost;
            heap.push((Reverse(*cost), s.clone()));
        }
    }

    while let Some((_, s)) = heap.pop() {
        let cur_cost = dist[&s];
        if is_palindrome(&s) {
            return cur_cost;
        }
        for (next, cost) in graph[&s].iter().map(|a| (&a.1, a.0)) {
            let next_cost = cur_cost + cost;
            let next_cur = dist.entry(next.clone()).or_insert(INF);
            if *next_cur > next_cost {
                *next_cur = next_cost;
                heap.push((Reverse(next_cost), next.clone()));
            }
        }
    }

    -1
}

fn prefix_length<T: PartialEq>(s: &[T], t: &[T]) -> usize {
    for i in 0.. {
        if i >= s.len() || i >= t.len() || s[i] != t[i] {
            return i;
        }
    }
    unreachable!()
}

fn is_palindrome<T: PartialEq>(s: &[T]) -> bool {
    let n = s.len();
    (0..n).all(|i| s[i] == s[n - 1 - i])
}

pub struct IO<R, W: std::io::Write>(R, std::io::BufWriter<W>);

impl<R: std::io::Read, W: std::io::Write> IO<R, W> {
    pub fn new(r: R, w: W) -> Self {
        Self(r, std::io::BufWriter::new(w))
    }
    pub fn write<S: ToString>(&mut self, s: S) {
        use std::io::Write;
        self.1.write_all(s.to_string().as_bytes()).unwrap();
    }
    pub fn read<T: std::str::FromStr>(&mut self) -> T {
        use std::io::Read;
        let buf = self
            .0
            .by_ref()
            .bytes()
            .map(|b| b.unwrap())
            .skip_while(|&b| b == b' ' || b == b'\n' || b == b'\r' || b == b'\t')
            .take_while(|&b| b != b' ' && b != b'\n' && b != b'\r' && b != b'\t')
            .collect::<Vec<_>>();
        unsafe { std::str::from_utf8_unchecked(&buf) }
            .parse()
            .ok()
            .expect("Parse error.")
    }
    pub fn vec<T: std::str::FromStr>(&mut self, n: usize) -> Vec<T> {
        (0..n).map(|_| self.read()).collect()
    }
    pub fn chars(&mut self) -> Vec<char> {
        self.read::<String>().chars().collect()
    }
}