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 rand::distributions::Uniform;
use rand::{thread_rng, Rng};
use std::cmp::{min, Reverse};
use std::collections::{BTreeMap, BinaryHeap};

const INF: i64 = 1 << 60;

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: usize = rng.sample(Uniform::from(1..=10));
    //     let mut t = vec![];
    //     for _ in 0..n {
    //         let length = rng.sample(Uniform::from(1..=5));
    //         let mut s = vec![];
    //         for _ in 0..length {
    //             let c: u8 = rng.sample(Uniform::from(0..26));
    //             let c = (c + 'a' as u8) as char;
    //             s.push(c);
    //         }
    //         let cost: i64 = rng.sample(Uniform::from(1..=1_000_000_000));
    //         t.push((s, cost));
    //     }
    //     assert_eq!(solve_1(&t), solve_wa(&t), "{:?}", t);
    //
    //     if i % 10000 == 0 {
    //         println!("{}", i);
    //     }
    // }

    let t = [("pr", 1), ("rpkrp", 10)]
        .iter()
        .map(|&(a, b)| (a.chars().collect::<Vec<_>>(), b))
        .collect::<Vec<_>>();

    assert_eq!(solve_1(&t), solve_wa(&t));

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

fn solve_wa(t: &[(Vec<char>, i64)]) -> i64 {
    let mut dp = BTreeMap::new();
    let mut heap = BinaryHeap::new();

    for (x, cost) in t.iter() {
        let cur = dp.entry(x.clone()).or_insert(INF);
        if *cur > *cost {
            *cur = *cost;
            heap.push((Reverse(*cost), x.clone()));
        }
    }

    let s = dp.iter().map(|(a, b)| (a.clone(), *b)).collect::<Vec<_>>();

    while let Some((_, search)) = heap.pop() {
        let cur_cost = dp[&search];
        if is_reversible(&search) {
            continue;
        }

        for (seg, cost) in s
            .iter()
            .map(|s| (s.0.iter().cloned().rev().collect::<Vec<_>>(), s.1))
        {
            let next_cost = cost + cur_cost;
            let prefix_length = prefix_length(&seg, &search);

            if prefix_length == seg.len() && prefix_length == search.len() {
                let cur = dp.entry(vec![]).or_insert(INF);
                if *cur > next_cost {
                    *cur = next_cost;
                    heap.push((Reverse(next_cost), vec![]));
                }
            } else if prefix_length == search.len() {
                let mut overflow = seg[prefix_length..].to_vec();
                // overflow.reverse();

                let cur = dp.entry(overflow.clone()).or_insert(INF);
                if *cur > next_cost {
                    *cur = next_cost;
                    heap.push((Reverse(next_cost), overflow));
                }
            } else if prefix_length == seg.len() {
                let remain = search[prefix_length..].to_vec();

                let cur = dp.entry(remain.clone()).or_insert(INF);
                if *cur > next_cost {
                    *cur = next_cost;
                    heap.push((Reverse(next_cost), remain));
                }
            }
        }
    }

    let mut ans = INF;
    for (s, v) in dp.into_iter() {
        if is_reversible(&s) {
            ans = min(ans, v);
        }
    }

    if ans == INF {
        -1
    } else {
        ans
    }
}

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_reversible<T: PartialEq>(a: &[T]) -> bool {
    let n = a.len();
    for i in 0..n {
        if a[i] != a[n - 1 - i] {
            return false;
        }
    }
    true
}
fn solve_1(t: &[(Vec<char>, i64)]) -> i64 {
    let mut dist = BTreeMap::new();
    let mut heap = BinaryHeap::new();
    for (s, cost) in t.iter() {
        let cur = dist.entry((s.clone(), true)).or_insert(INF);
        if *cur > *cost {
            *cur = *cost;
            heap.push((Reverse(*cost), s.clone(), true));
        }
    }

    let mut ans = INF;
    while let Some((_, s, is_head)) = heap.pop() {
        let cur_cost = dist[&(s.clone(), is_head)];

        if is_palindrome(&s) {
            ans = min(ans, cur_cost);
        }

        for (edge, cost) in t.iter() {
            let next_cost = cur_cost + cost;
            if is_head {
                let tail_length = match_length(&s, &edge);
                if s.len() == tail_length && edge.len() == tail_length {
                    let next = vec![];
                    let cur = dist.entry((next.clone(), true)).or_insert(INF);
                    if *cur > next_cost {
                        *cur = next_cost;
                        heap.push((Reverse(next_cost), next.clone(), true));
                    }
                    let cur = dist.entry((next.clone(), false)).or_insert(INF);
                    if *cur > next_cost {
                        *cur = next_cost;
                        heap.push((Reverse(next_cost), next.clone(), false));
                    }
                } else if tail_length == s.len() {
                    let next = edge[..(edge.len() - tail_length)].to_vec();
                    let next_forward = false;
                    let cur = dist.entry((next.clone(), next_forward)).or_insert(INF);
                    if *cur > next_cost {
                        *cur = next_cost;
                        heap.push((Reverse(next_cost), next.clone(), next_forward));
                    }
                } else if tail_length == edge.len() {
                    let next = s[tail_length..].to_vec();
                    let next_forward = true;
                    let cur = dist.entry((next.clone(), next_forward)).or_insert(INF);
                    if *cur > next_cost {
                        *cur = next_cost;
                        heap.push((Reverse(next_cost), next.clone(), next_forward));
                    }
                }
            } else {
                let head_length = match_length(&edge, &s);
                if head_length == edge.len() && head_length == s.len() {
                    let next = vec![];
                    let cur = dist.entry((next.clone(), true)).or_insert(INF);
                    if *cur > next_cost {
                        *cur = next_cost;
                        heap.push((Reverse(next_cost), next.clone(), true));
                    }
                    let cur = dist.entry((next.clone(), false)).or_insert(INF);
                    if *cur > next_cost {
                        *cur = next_cost;
                        heap.push((Reverse(next_cost), next.clone(), false));
                    }
                } else if head_length == edge.len() {
                    let next = s[..(s.len() - head_length)].to_vec();
                    let next_forward = false;
                    let cur = dist.entry((next.clone(), next_forward)).or_insert(INF);
                    if *cur > next_cost {
                        *cur = next_cost;
                        heap.push((Reverse(next_cost), next.clone(), next_forward));
                    }
                } else if head_length == s.len() {
                    let next = edge[head_length..].to_vec();
                    let next_forward = true;
                    let cur = dist.entry((next.clone(), next_forward)).or_insert(INF);
                    if *cur > next_cost {
                        *cur = next_cost;
                        heap.push((Reverse(next_cost), next.clone(), next_forward));
                    }
                }
            }
        }
    }

    if ans == INF {
        -1
    } else {
        ans
    }
}

fn match_length<T: PartialEq>(head: &[T], tail: &[T]) -> usize {
    for i in 0.. {
        if i >= head.len() || i >= tail.len() || head[i] != tail[tail.len() - 1 - i] {
            return i;
        }
    }
    unimplemented!()
}
fn is_palindrome<T: PartialEq>(s: &[T]) -> bool {
    let n = s.len();
    for i in 0..n {
        if s[i] != s[n - 1 - i] {
            return false;
        }
    }
    true
}

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()
    }
}