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: Accepted)

pub trait IterScan: Sized {
    type Output;
    fn scan<'a, I: Iterator<Item = &'a str>>(iter: &mut I) -> Option<Self::Output>;
}
pub trait MarkedIterScan: Sized {
    type Output;
    fn mscan<'a, I: Iterator<Item = &'a str>>(self, iter: &mut I) -> Option<Self::Output>;
}
#[derive(Debug)]
pub struct Scanner<'a> {
    iter: std::str::SplitAsciiWhitespace<'a>,
}
impl<'a> Scanner<'a> {
    #[inline]
    pub fn new(s: &'a str) -> Self {
        let iter = s.split_ascii_whitespace();
        Self { iter }
    }
    #[inline]
    pub fn scan<T: IterScan>(&mut self) -> <T as IterScan>::Output {
        T::scan(&mut self.iter).unwrap()
    }
    #[inline]
    pub fn mscan<T: MarkedIterScan>(&mut self, marker: T) -> <T as MarkedIterScan>::Output {
        marker.mscan(&mut self.iter).unwrap()
    }
    #[inline]
    pub fn scan_vec<T: IterScan>(&mut self, size: usize) -> Vec<<T as IterScan>::Output> {
        (0..size)
            .map(|_| T::scan(&mut self.iter).unwrap())
            .collect()
    }
    #[inline]
    pub fn scan_chars(&mut self) -> Vec<char> {
        self.iter.next().unwrap().chars().collect::<Vec<char>>()
    }
    #[inline]
    pub fn scan_chars_with(&mut self, base: char) -> Vec<usize> {
        self.iter
            .next()
            .unwrap()
            .chars()
            .map(|c| (c as u8 - base as u8) as usize)
            .collect::<Vec<usize>>()
    }
}
mod scanner_impls {
    use super::*;
    macro_rules !iter_scan_impls {($($t :ty ) *) =>{$(impl IterScan for $t {type Output =Self ;#[inline ] fn scan <'a ,I :Iterator <Item =&'a str >>(iter :&mut I ) ->Option <Self >{iter .next () ?.parse ::<$t >() .ok () } } ) *} ;}
    iter_scan_impls !(char u8 u16 u32 u64 usize i8 i16 i32 i64 isize f32 f64 u128 i128 String ) ;
    macro_rules !iter_scan_tuple_impl {($($T :ident ) *) =>{impl <$($T :IterScan ) ,*>IterScan for ($($T ,) *) {type Output =($(<$T as IterScan >::Output ,) *) ;#[inline ] fn scan <'a ,It :Iterator <Item =&'a str >>(_iter :&mut It ) ->Option <Self ::Output >{Some (($($T ::scan (_iter ) ?,) *) ) } } } ;}
    iter_scan_tuple_impl!();
    iter_scan_tuple_impl!(A);
    iter_scan_tuple_impl !(A B ) ;
    iter_scan_tuple_impl !(A B C ) ;
    iter_scan_tuple_impl !(A B C D ) ;
    iter_scan_tuple_impl !(A B C D E ) ;
    iter_scan_tuple_impl !(A B C D E F ) ;
    iter_scan_tuple_impl !(A B C D E F G ) ;
    iter_scan_tuple_impl !(A B C D E F G H ) ;
    iter_scan_tuple_impl !(A B C D E F G H I ) ;
    iter_scan_tuple_impl !(A B C D E F G H I J ) ;
    iter_scan_tuple_impl !(A B C D E F G H I J K ) ;
    pub struct ScannerIter<'a, 'b, T> {
        inner: &'b mut Scanner<'a>,
        _marker: std::marker::PhantomData<fn() -> T>,
    }
    impl<'a, 'b, T: IterScan> Iterator for ScannerIter<'a, 'b, T> {
        type Item = <T as IterScan>::Output;
        fn next(&mut self) -> Option<Self::Item> {
            T::scan(&mut self.inner.iter)
        }
    }
    impl<'a> Scanner<'a> {
        #[inline]
        pub fn iter<'b, T: IterScan>(&'b mut self) -> ScannerIter<'a, 'b, T> {
            ScannerIter {
                inner: self,
                _marker: std::marker::PhantomData,
            }
        }
    }
}
pub mod marker {
    use super::*;
    pub struct Usize1;
    impl IterScan for Usize1 {
        type Output = usize;
        #[inline]
        fn scan<'a, I: Iterator<Item = &'a str>>(iter: &mut I) -> Option<Self::Output> {
            usize::scan(iter).map(|x| x.wrapping_sub(1))
        }
    }
    pub struct Isize1;
    impl IterScan for Isize1 {
        type Output = isize;
        #[inline]
        fn scan<'a, I: Iterator<Item = &'a str>>(iter: &mut I) -> Option<Self::Output> {
            isize::scan(iter).map(|x| x.wrapping_sub(1))
        }
    }
}
#[macro_export]
macro_rules !min {($e :expr ) =>{$e } ;($e :expr ,$($es :expr ) ,+) =>{std ::cmp ::min ($e ,min !($($es ) ,+) ) } ;}
#[macro_export]
macro_rules !chmin {($dst :expr ,$($src :expr ) ,+) =>{{let x =std ::cmp ::min ($dst ,min !($($src ) ,+) ) ;$dst =x ;} } ;}
#[macro_export]
macro_rules !max {($e :expr ) =>{$e } ;($e :expr ,$($es :expr ) ,+) =>{std ::cmp ::max ($e ,max !($($es ) ,+) ) } ;}
#[macro_export]
macro_rules !chmax {($dst :expr ,$($src :expr ) ,+) =>{{let x =std ::cmp ::max ($dst ,max !($($src ) ,+) ) ;$dst =x ;} } ;}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct Adjacent {
    pub id: usize,
    pub to: usize,
}
impl Adjacent {
    pub fn new(id: usize, to: usize) -> Adjacent {
        Adjacent { id, to }
    }
}
#[derive(Clone, Debug, Default)]
pub struct Graph {
    pub vsize: usize,
    pub esize: usize,
    pub graph: Vec<Vec<Adjacent>>,
}
impl Graph {
    pub fn new(vsize: usize) -> Graph {
        Graph {
            vsize,
            esize: 0,
            graph: vec![vec![]; vsize],
        }
    }
    pub fn add_edge(&mut self, from: usize, to: usize) {
        self.graph[from].push(Adjacent::new(self.esize, to));
        self.esize += 1;
    }
    pub fn add_undirected_edge(&mut self, u: usize, v: usize) {
        self.graph[u].push(Adjacent::new(self.esize, v));
        self.graph[v].push(Adjacent::new(self.esize, u));
        self.esize += 1;
    }
    pub fn vertices(&self) -> std::ops::Range<usize> {
        0..self.vsize
    }
    pub fn adjacency(&self, from: usize) -> &Vec<Adjacent> {
        &self.graph[from]
    }
    pub fn eid_cache(&self) -> GraphEidCache<'_> {
        let mut cache = vec![(0, 0); self.esize];
        for u in self.vertices() {
            for a in self.adjacency(u) {
                cache[a.id] = (u, a.to);
            }
        }
        GraphEidCache { graph: self, cache }
    }
}
pub struct GraphScanner<U: IterScan<Output = usize>, T: IterScan> {
    vsize: usize,
    esize: usize,
    directed: bool,
    _marker: std::marker::PhantomData<fn() -> (U, T)>,
}
impl<U: IterScan<Output = usize>, T: IterScan> GraphScanner<U, T> {
    pub fn new(vsize: usize, esize: usize, directed: bool) -> Self {
        Self {
            vsize,
            esize,
            directed,
            _marker: std::marker::PhantomData,
        }
    }
}
impl<U: IterScan<Output = usize>, T: IterScan> MarkedIterScan for GraphScanner<U, T> {
    type Output = (Graph, Vec<<T as IterScan>::Output>);
    fn mscan<'a, I: Iterator<Item = &'a str>>(self, iter: &mut I) -> Option<Self::Output> {
        let mut graph = Graph::new(self.vsize);
        let mut rest = Vec::with_capacity(self.esize);
        for _ in 0..self.esize {
            let (from, to) = (U::scan(iter)?, U::scan(iter)?);
            if self.directed {
                graph.add_edge(from, to);
            } else {
                graph.add_undirected_edge(from, to);
            }
            rest.push(T::scan(iter)?);
        }
        Some((graph, rest))
    }
}
#[derive(Debug, Clone)]
pub struct GraphEidCache<'a> {
    graph: &'a Graph,
    cache: Vec<(usize, usize)>,
}
impl<'a> std::ops::Index<usize> for GraphEidCache<'a> {
    type Output = (usize, usize);
    fn index(&self, index: usize) -> &Self::Output {
        &self.cache[index]
    }
}
/// binary operaion: $T \circ T \to T$
pub trait Magma {
    /// type of operands: $T$
    type T: Clone + PartialEq;
    /// binary operaion: $\circ$
    fn operate(&self, x: &Self::T, y: &Self::T) -> Self::T;
    #[inline]
    fn reverse_operate(&self, x: &Self::T, y: &Self::T) -> Self::T {
        self.operate(x, y)
    }
}
/// $\forall a,\forall b,\forall c \in T, (a \circ b) \circ c = a \circ (b \circ c)$
pub trait Associative {}
/// associative binary operation
pub trait SemiGroup: Magma + Associative {}
impl<S: Magma + Associative> SemiGroup for S {}
/// $\exists e \in T, \forall a \in T, e \circ a = a \circ e = e$
pub trait Unital: Magma {
    /// identity element: $e$
    fn unit(&self) -> Self::T;
}
/// associative binary operation and an identity element
pub trait Monoid: SemiGroup + Unital {
    /// binary exponentiation: $x^n = x\circ\ddots\circ x$
    fn pow(&self, x: Self::T, n: usize) -> Self::T {
        let mut n = n;
        let mut res = self.unit();
        let mut base = x;
        while n > 0 {
            if n & 1 == 1 {
                res = self.operate(&res, &base);
            }
            base = self.operate(&base, &base);
            n = n >> 1;
        }
        res
    }
}
impl<M: SemiGroup + Unital> Monoid for M {}
/// $\exists e \in T, \forall a \in T, \exists b,c \in T, b \circ a = a \circ c = e$
pub trait Invertible: Magma {
    /// $a$ where $a \circ x = e$
    fn inverse(&self, x: &Self::T) -> Self::T;
}
/// short cut of right inverse binary operation
pub trait RightInvertibleMagma: Magma {
    /// right inverse binary operation: $\cdot \circ (\cdot^{-1})$
    fn rinv_operation(&self, x: &Self::T, y: &Self::T) -> Self::T;
}
/// associative binary operation and an identity element and inverse elements
pub trait Group: Monoid + Invertible {}
impl<G: Monoid + Invertible> Group for G {}
/// $\forall a,\forall b \in T, a \circ b = b \circ a$
pub trait Commutative {}
/// commutative monoid
pub trait AbelianMonoid: Monoid + Commutative {}
impl<M: Monoid + Commutative> AbelianMonoid for M {}
/// commutative group
pub trait AbelianGroup: Group + Commutative {}
impl<G: Group + Commutative> AbelianGroup for G {}
/// $\forall a \in T, a \circ a = a$
pub trait Idempotent {}
/// idempotent monoid
pub trait IdempotentMonoid: Monoid + Idempotent {}
impl<M: Monoid + Idempotent> IdempotentMonoid for M {}
/// $+$
#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct AdditiveOperation<T: Copy + PartialEq + AdditiveIdentity> {
    _marker: std::marker::PhantomData<fn() -> T>,
}
pub trait AdditiveIdentity: Sized + std::ops::Add<Output = Self> {
    fn zero() -> Self;
}
#[macro_use]
mod additive_operation_impl {
    use super::*;
    #[macro_export(local_inner_macros)]
    macro_rules !impl_additive_identity {([$($wh :tt ) *] ,$t :ty ,$zero :expr ) =>{impl <$($wh ) *>AdditiveIdentity for $t {#[inline ] fn zero () ->Self {$zero } } } ;($t :ty ,$zero :expr ) =>{impl AdditiveIdentity for $t {#[inline ] fn zero () ->Self {$zero } } } ;}
    impl_additive_identity!(usize, 0usize);
    impl_additive_identity!(u8, 0u8);
    impl_additive_identity!(u16, 0u16);
    impl_additive_identity!(u32, 0u32);
    impl_additive_identity!(u64, 0u64);
    impl_additive_identity!(isize, 0isize);
    impl_additive_identity!(i8, 0i8);
    impl_additive_identity!(i16, 0i16);
    impl_additive_identity!(i32, 0i32);
    impl_additive_identity!(i64, 0i64);
    impl_additive_identity!(f32, 0.0f32);
    impl_additive_identity!(f64, 0.0f64);
    impl<T: Copy + PartialEq + AdditiveIdentity> AdditiveOperation<T> {
        pub fn new() -> Self {
            Self {
                _marker: std::marker::PhantomData,
            }
        }
    }
    impl<T: Copy + PartialEq + AdditiveIdentity> Magma for AdditiveOperation<T> {
        type T = T;
        #[inline]
        fn operate(&self, x: &Self::T, y: &Self::T) -> Self::T {
            *x + *y
        }
    }
    impl<T: Copy + PartialEq + AdditiveIdentity> Unital for AdditiveOperation<T> {
        #[inline]
        fn unit(&self) -> Self::T {
            AdditiveIdentity::zero()
        }
    }
    impl<T: Copy + PartialEq + AdditiveIdentity> Associative for AdditiveOperation<T> {}
    impl<T: Copy + PartialEq + AdditiveIdentity> Commutative for AdditiveOperation<T> {}
    impl<T: Copy + PartialEq + AdditiveIdentity + std::ops::Neg<Output = T>> Invertible
        for AdditiveOperation<T>
    {
        #[inline]
        fn inverse(&self, x: &Self::T) -> Self::T {
            -*x
        }
    }
    impl<T: Copy + PartialEq + AdditiveIdentity + std::ops::Sub<Output = T>> RightInvertibleMagma
        for AdditiveOperation<T>
    {
        #[inline]
        fn rinv_operation(&self, x: &Self::T, y: &Self::T) -> Self::T {
            *x - *y
        }
    }
}
impl Graph {
    pub fn dijkstra<M: Monoid, F: Fn(usize) -> M::T>(
        &self,
        start: usize,
        monoid: M,
        weight: F,
    ) -> Vec<Option<M::T>>
    where
        M::T: Ord,
    {
        use std::cmp::Reverse;
        let mut cost = vec![None; self.vsize];
        let mut heap = std::collections::BinaryHeap::new();
        cost[start] = Some(monoid.unit());
        heap.push((Reverse(monoid.unit()), start));
        while let Some((Reverse(d), u)) = heap.pop() {
            if cost[u].as_ref().unwrap() < &d {
                continue;
            }
            for a in self.adjacency(u) {
                let nd = monoid.operate(&d, &weight(a.id));
                if cost[a.to].as_ref().map_or(true, |c| c > &nd) {
                    cost[a.to] = Some(nd.clone());
                    heap.push((Reverse(nd), a.to));
                }
            }
        }
        cost
    }
}
fn main() {
    #[allow(unused_imports)]
    use std::io::{Read as _, Write as _};
    let __out = std::io::stdout();
    let mut __in_buf = String::new();
    std::io::stdin().read_to_string(&mut __in_buf).unwrap();
    let mut scanner = Scanner::new(&__in_buf);
    #[allow(unused_macros)]
    macro_rules !scan {() =>{scan !(usize ) } ;(($($t :tt ) ,*) ) =>{($(scan !($t ) ) ,*) } ;([$t :tt ;$len :expr ] ) =>{(0 ..$len ) .map (|_ |scan !($t ) ) .collect ::<Vec <_ >>() } ;({chars :$b :expr } ) =>{scanner .scan_chars_with ($b ) } ;({$t :tt =>$f :expr } ) =>{$f (scan !($t ) ) } ;(chars ) =>{scanner .scan_chars () } ;($t :ty ) =>{scanner .scan ::<$t >() } ;}
    let mut __out = std::io::BufWriter::new(__out.lock());
    #[allow(unused_macros)]
    macro_rules !print {($($arg :tt ) *) =>(::std ::write !(__out ,$($arg ) *) .unwrap () ) }
    #[allow(unused_macros)]
    macro_rules !println {($($arg :tt ) *) =>(::std ::writeln !(__out ,$($arg ) *) .unwrap () ) }
    #[allow(unused_macros)]
    macro_rules! echo {
        ($iter :expr ) => {
            echo!($iter, "\n")
        };
        ($iter :expr ,$sep :expr ) => {
            let mut iter = $iter;
            if let Some(item) = iter.next() {
                print!("{}", item);
            }
            for item in iter {
                print!("{}{}", $sep, item);
            }
            println!();
        };
    }
    let n = scan!();
    let sc = scan!([({chars: 'a'}, u64); n]);
    let mut g = Graph::new(n * 39 + 2);
    let mut ans = std::u64::MAX;
    let mut ok = vec![false; 39 * n + 2];
    let mut cost = vec![];
    let check = |s: &[usize], t: &[usize]| s.iter().zip(t.iter().rev()).all(|(a, b)| a == b);
    let check2 = |s: &[usize]| {
        let m = s.len();
        check(&s[..m / 2], &s[m - m / 2..])
    };
    let right = |i: usize, x: usize| i * 39 + x - 1;
    let left = |i: usize, x: usize| i * 39 + 2 * sc[i].0.len() - x - 1;
    let middle = |i: usize| i * 39 + sc[i].0.len() - 1;
    let start = n * 39;
    let goal = n * 39 + 1;
    ok[goal] = true;
    for (i, (s, c)) in sc.iter().enumerate() {
        let m = s.len();
        for j in 1..m {
            let u = right(i, j);
            let ss = &s[m - j..m];
            ok[u] = check2(ss);
            for (k, (t, d)) in sc.iter().enumerate() {
                let l = t.len();
                let v = match j.cmp(&l) {
                    std::cmp::Ordering::Less => left(k, l - j),
                    std::cmp::Ordering::Equal => goal,
                    std::cmp::Ordering::Greater => right(i, j - l),
                };
                if check(&ss[..j.min(l)], &t[l - j.min(l)..]) {
                    g.add_edge(u, v);
                    cost.push(*d);
                }
            }
        }
        if check2(&s) {
            ok[middle(i)] = true;
            g.add_edge(start, middle(i));
            cost.push(*c);
        }
        for j in 1..m {
            let u = left(i, j);
            let ss = &s[0..j];
            ok[u] = check2(ss);
            for (k, (t, d)) in sc.iter().enumerate() {
                let l = t.len();
                let v = match j.cmp(&l) {
                    std::cmp::Ordering::Less => right(k, l - j),
                    std::cmp::Ordering::Equal => goal,
                    std::cmp::Ordering::Greater => left(i, j - l),
                };
                if check(&ss[j - j.min(l)..], &t[..j.min(l)]) {
                    g.add_edge(u, v);
                    cost.push(*d);
                }
            }
        }
        for (k, (t, d)) in sc.iter().enumerate() {
            let l = t.len();
            let v = match m.cmp(&l) {
                std::cmp::Ordering::Less => left(k, l - m),
                std::cmp::Ordering::Equal => goal,
                std::cmp::Ordering::Greater => right(i, m - l),
            };
            if check(&s[..m.min(l)], &t[l - m.min(l)..]) {
                g.add_edge(start, v);
                cost.push(c + d);
            }
        }
    }
    let v = g.dijkstra(start, AdditiveOperation::new(), |i| cost[i]);
    for (c, b) in v.into_iter().zip(ok.into_iter()) {
        if b {
            if let Some(c) = c {
                chmin!(ans, c);
            }
        }
    }
    println!("{}", if ans == std::u64::MAX { -1 } else { ans as i64 });
}