#include <bits/stdc++.h>
using namespace std;

#pragma region Macros
using ll = long long;
#define int ll
using pii = pair<int, int>;
using tiii = tuple<int, int, int>;
template<class T = ll> using V = vector<T>;
template<class T = ll> using VV = V<V<T>>;
#define IOS\
    ios::sync_with_stdio(false);\
    cin.tie(0);\
    cout.tie(0);
#define FOR(i,l,r) for(ll i=(l);i<(r);++i)
#define REP(i,n) FOR(i,0,n)
#define REPS(i,n) FOR(i,1,n+1)
#define RFOR(i,l,r) for(ll i=(l);i>=(r);--i)
#define RREP(i,n) RFOR(i,n-1,0)
#define RREPS(i,n) RFOR(i,n,1)
#define mp make_pair
#define mt make_tuple
#define pb push_back
#define eb emplace_back
#define all(x) (x).begin(),(x).end()
#define SORT(name) sort(name.begin(), name.end())
#define RSORT(name)\
    SORT(name);\
    reverse(all(name));
#define ZERO(p) memset(p, 0, sizeof(p))
#define MINUS(p) memset(p, -1, sizeof(p))
inline void Yes(bool b = true) {cout << (b ? "Yes" : "No") << '\n';}
inline void YES(bool b = true) {cout << (b ? "YES" : "NO") << '\n';}
template <class T> inline void print(T x){ cout << x << '\n';}
template<typename T1,typename T2> inline void chmin(T1 &a, T2 b){ if(a > b) a = b; }
template<typename T1,typename T2> inline void chmax(T1 &a, T2 b){ if(a < b) a = b; }

const ll LLINF = (1LL<<60);
const int INF = (1LL<<30);
const double DINF = std::numeric_limits<double>::infinity();
#pragma endregion

template <typename T>
class Dijkstra {
    private:
        struct Edge {
            int to;   // 有向辺の行先頂点
            T cost;  // 辺の重み
        };
        typedef pair<int, T> P;       // first: 最短距離 second: 頂点 id
    public:
        // コンストラクタ
        // v … 頂点数, inf: …「無限大」とする値
        Dijkstra() {}
        Dijkstra(int v, T inf) { Init(v, inf); }
        void Init(int v, T inf) {
            assert(v > 0);
            m_V = v;
            m_Inf = inf;
            m_G.assign(m_V, vector<Edge>());
        }
        // 有向辺の追加。負のコストは未対応
        void AddDirEdge(int from, int to, T cost) {
            assert(0 <= from && from < m_V);
            assert(0 <= to && to < m_V);
            m_G[from].push_back({to, cost});
        }
        // 無向辺の追加。負のコストは未対応
        void AddUndirEdge(int from, int to, T cost) {
            AddDirEdge(from, to, cost);
            AddDirEdge(to, from, cost);
        }
        // 始点 s から各頂点への最短経路の導出 O(E log V)
        // Calc を連続で行うことはできない。その場合は m_Dist と m_Prev の再初期化が必要
        void Calc(int s) {
            assert(0 <= s && s < m_V);
            m_Dist.assign(m_V, m_Inf);
            m_PrevP.assign(m_V, -1);
            // 最短距離が短い順で cand を取り出す
            // 頂点 P.second まで P.first のコストで来れたことを情報として入れておく
            priority_queue<P, vector<P>, greater<P> > cand;
            m_Dist[s] = 0;  // 始点のコストは 0
            cand.push(P(0, s));
            while(!cand.empty()) {
                P p = cand.top(); cand.pop();     // cand のトップを取り出す
                T v = p.second;
                // 頂点 v まで最短経路が、 cand から取り出したものより既に小さいなら何もしない
                if(m_Dist[v] < p.first){ continue; }
                // 現在見ている頂点から延びている辺を全て見る
                for(Edge e : m_G[v]) {
                    if(m_Dist[e.to] > m_Dist[v] + e.cost) {
                        // 現在の最短経路より短くなるなら更新
                        m_Dist[e.to] = m_Dist[v] + e.cost;
                        m_PrevP[e.to] = v;
                        cand.push(P(m_Dist[e.to], e.to));
                    }
                }
            }
        }
        // 始点 s から頂点 t への最短距離を返す O(1)
        // 前処理として Calc(s) が必要
        T GetDist(int t) { return m_Dist[t]; }
        // 始点 s から頂点 t までの経路を vector に入れて返す (s と t 含む)
        // O(V') … V' は最短経路の頂点数
        // 前処理として Calc(s) が必要
        vector<int> GetPath(int t) {
            vector<T> path;
            for(; t != -1; t = m_PrevP[t]) { path.push_back(t); }  // t が s になるまで prev_p[t] を辿っていく
            // このままだと t->s の順になっているので逆順にする
            reverse(path.begin(), path.end());
            return path;
        }
    private:
        int m_V;                    // 頂点の数
        T m_Inf;                    // 無限の代わりとして扱う値
        vector< vector<Edge> > m_G; // 頂点 i から延びている辺の vector(隣接リスト)
        vector<T> m_Dist;           // 各頂点への最短コスト。つまり答え
        vector<int> m_PrevP;        // 最短経路の各頂点を保存(経路復元で利用)
};

#if 0
#  define DBG(fmt, ...) printf(fmt, ##__VA_ARGS__)
#else
#  define DBG(fmt, ...)
#endif

const int MOD = 1000000007;
const int MAX_N = 100010;

// 与えられた文字列を重複を許して合計 1 個以上選んだ後、
// 選んだ文字列を好きな順番で連結することで、回文が作れるか試す。
// (ABC175 F)
// [引数]
// sc:  文字列の集合。 first が文字列 second が利用コスト（もしあれば）
// [返り値]
// 回文が作成可能なら作成にかかる最小コスト(0以上)を返す
// 回文が作成不可能なら -1 を返す
// [詳細]
// O(N^2|S|) 程度。（N: sc.length(), |S|: sc の文字列の長さの最長)
// 別途ダイクストラライブラリが必要

int MakePalindrome(const vector< pair<string, int> >& sc) {
    // s の reverse を返す
    auto GetReverse = [](string s) -> string {
        reverse(s.begin(), s.end());
        return s;
    };
    // 回文なら true
    auto IsPalindrome = [&GetReverse](string s) -> bool {
        return (s == GetReverse(s));
    };

    // s の逆順文字列
    vector<string> s_r;
    for(auto p : sc) {
        s_r.emplace_back(GetReverse(p.first));
    }

    // 基本方針：
    // 文字列を左端もしくは右端に配置し、回文になる際に余る文字列のパターンを
    // 頂点と考えたグラフを作成。
    // そのグラフの最短距離が求まるなら、回文作成可能。
    int node_size = 0;  // 頂点数
    // 頂点 id
    // k = 0 -> s[i] の [j, Si.length()-1] 文字が左側に余っているときの頂点
    // k = 1 -> s_r[i] の [j, S_ri.length()-1] 文字が右側に余っているときの頂点
    vector< vector< vector<int> > > id;
    for(int i = 0; i < static_cast<int>(sc.size()); ++i) {
        id.emplace_back(vector< vector<int> >(sc[i].first.length(), vector<int>(2, 0)));
        for(int j = 0; j < static_cast<int>(sc[i].first.length()); ++j) {
            id[i][j][0] = node_size;
            node_size++;
            id[i][j][1] = node_size;
            node_size++;
        }
    }

    // 辺の追加
    Dijkstra<int> dk(node_size + 10, LLINF);
    int n_start = node_size + 2;
    int n_goal = node_size + 3;

    for(int i = 0; i < static_cast<int>(sc.size()); ++i) {
        dk.AddDirEdge(n_start, id[i][0][0], sc[i].second);
        if(IsPalindrome(sc[i].first)) {
            dk.AddDirEdge(id[i][0][0], n_goal, 0);
            dk.AddDirEdge(id[i][0][1], n_goal, 0);
        }
    }

    for(int i = 0; i < static_cast<int>(sc.size()); ++i) {
        for(int j = 0; j < static_cast<int>(sc[i].first.length()); ++j) {
            // 余っている文字
            string rem1 = sc[i].first.substr(j, sc[i].first.length() - j);
            string rem2 = s_r[i].substr(j, s_r[i].length() - j);
            // 余りが回文ならこれ以上追加する必要はない
            if (IsPalindrome(rem1)) {
                dk.AddDirEdge(id[i][j][0], n_goal, 0);
            }
            if (IsPalindrome(rem2)) {
                dk.AddDirEdge(id[i][j][1], n_goal, 0);
            }
            // 余っている文字の prefix が S_r の prefix に出てくる頂点に遷移できる
            for(int k = 0; k < static_cast<int>(sc.size()); ++k) {
                if(rem1.length() > s_r[k].length()) {
                    string cand = rem1.substr(0, s_r[k].length());
                    if(cand == s_r[k]) {
                        dk.AddDirEdge(id[i][j][0], id[i][j + cand.length()][0], sc[k].second);
                    }
                }
                else if(rem1.length() < s_r[k].length()) {
                    string cand = s_r[k].substr(0, rem1.length());
                    if(rem1 == cand) {
                        // 余りがでるサイドが変わる
                        dk.AddDirEdge(id[i][j][0], id[k][cand.length()][1], sc[k].second);
                    }
                }
                else if(rem1 == s_r[k]) {
                    // ゴール
                    dk.AddDirEdge(id[i][j][0], n_goal, sc[k].second);
                }

                if(rem2.length() > sc[k].first.length()) {
                    string cand = rem2.substr(0, sc[k].first.length());
                    if(cand == sc[k].first) {
                        dk.AddDirEdge(id[i][j][1], id[i][j + cand.length()][1], sc[k].second);
                    }
                }
                else if(rem2.length() < sc[k].first.length()) {
                    string cand = sc[k].first.substr(0, rem2.length());
                    if(rem2 == cand) {
                        dk.AddDirEdge(id[i][j][1], id[k][cand.length()][0], sc[k].second);
                    }
                }
                else if(rem2 == sc[k].first) {
                    dk.AddDirEdge(id[i][j][1], n_goal, sc[k].second);
                }
            }
        }
    }

    dk.Calc(n_start);
    int ret = dk.GetDist(n_goal);
    return (ret >= LLINF) ? -1 : ret;
}

int N;
V< pair<string, int> > SC;

signed main() {
    IOS;

    cin >> N;
    REP(i, N) {
        string s;
        int c;
        cin >> s >> c;
        SC.pb(mp(s, c));
    }
    print(MakePalindrome(SC));

    return 0;
}


Measured memory_bytes: 3768