#include <bits/stdc++.h>
// #include <ext/pb_ds/assoc_container.hpp>
// #include <ext/pb_ds/tree_policy.hpp>
// #include <ext/pb_ds/priority_queue.hpp>
//#pragma GCC optimize("O3")
//#pragma GCC target("sse4")
//#pragma GCC target("avx2")

#define deb(x) cout << #x << " = " << x << "\n"
#define aint(x) (x).begin(), (x).end()
#define len(x) (int) x.size()
#define lsb(x) (x) & -(x)
#define l(x) (x << 1) + 1
#define r(x) (x << 1) + 2
#define v(x) (int)(x - 'a')

#define xx first
#define yy second
#define mp make_pair
#define pb push_back
#define lb lower_bound
#define ub upper_bound

using namespace std;
// using namespace __gnu_pbds;
typedef long long ll;
typedef long double ld;
typedef pair <int, int> pii;
typedef pair <ld, ld> pld;
typedef pair <int, int> pint;

// template <class T1, class T2 = less <T1>> using pb_heap = __gnu_pbds::priority_queue <T1, T2, binary_heap_tag>;
// template <class T1, class T2 = nuint_type, class T3 = less <T1>> using pb_map = tree <T1, T2, T3, rb_tree_tag, tree_order_statistics_node_update>;
// template <class T1, class T2 = nuint_type, class T3 = hash <T1>> using pb_umap = gp_hash_table <T1, T2, T3>;
template <class T1, class T2, class T3 = hash <T1>> using umap = unordered_map <T1, T2, T3>;
template <class T> using uset = unordered_set <T>;
template <class T> using vec = vector <T>;

const ll infll = numeric_limits <ll>::max() >> 1;
const int inf = numeric_limits <int>::max() >> 1;
const int M = 1 << 14;
const int N = 1001;
const int L = 14;
const int K = 15;
int n, q, cur;

ll dp[M][N];
ll ut[M][N];
int in[K];

string s;

struct Trie {
    int gon[N][L];
    int nxt[N][L];
    int link[N];
    int chr[N];
    int par[N];
    int cnt[N];
    ll sum[N];

    Trie() {
        memset(link, -1, sizeof link);
        memset(gon, -1, sizeof gon);
    }
} trie;

ll calc(int, int);
int link(int);
int go(int, int);

void insert(string &s, int p) {
    int node = 0;
    for(char c: s) {
        int i = v(c);
        if(!trie.nxt[node][i]) {
            trie.nxt[node][i] = ++cur;
            trie.par[cur] = node;
            trie.chr[cur] = i;
        }
        node = trie.nxt[node][i];
    }
    trie.sum[node] += p;
}

int link(int node) {
    if(trie.link[node] == -1) {
        if(!node || !trie.par[node]) {
            trie.link[node] = 0;
        } else {
            trie.link[node] = go(link(trie.par[node]), trie.chr[node]);
            link(trie.link[node]);
            trie.sum[node] += trie.sum[trie.link[node]];
        }
    }
    return trie.link[node];
}

int go(int node, int i) {
    if(trie.gon[node][i] == -1) {
        if(trie.nxt[node][i]) {
            trie.gon[node][i] =
            trie.nxt[node][i];
        } else {
            trie.gon[node][i] = node? go(link(node), i): 0;
        }
    }
    return trie.gon[node][i];
}

void input() {
    cin >> q;
    for(int i = 0; i < q; ++i) {
        int p;
        string t; cin >> t >> p;
        insert(t, p);
    }
    cin >> s;
    for(int i = 0; i <= cur; ++i) {
        link(i);
    }
    n = len(s);
    fill(in, in + K, n);
    for(int i = 0, j = 0; i < n; ++i) {
        if(s[i] == '?') {
            in[j++] = i;
        }
    }
}

int get(int x) {
    return in[14 - __builtin_popcount(x)];
}

ll compute(int i, int mask, int node) {
    if(ut[mask][node] != -infll) {
        return ut[mask][node];
    }
    int omask = mask;
    int onode = node;
    ll res = 0;
    int rig = get(mask);
    for(i++; i < rig; ++i) {
        res += trie.sum[node];
        node = go(node, v(s[i]));
    }
    return ut[omask][onode] = calc(mask, node) + res;
}

ll calc(int i, int j) {
    int x = get(i);
    if(x == n) {
        return trie.sum[j];
    }
    if(dp[i][j] != -infll) {
        return dp[i][j];
    }
    ll res = -inf;
    for(int c = 0; c < L; ++c) {
        if((i >> c) & 1) {
            res = max(res, compute(x, i ^ (1 << c), go(j, c)));
        }
    }
    return dp[i][j] = res + trie.sum[j];
}

void solve() {
    fill(dp[0], dp[M], -infll);
    fill(ut[0], ut[M], -infll);
    cout << compute(-1, (1 << 14) - 1, 0) << "\n";
}

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    input();
    solve();
}


#include <bits/stdc++.h>
// #include <ext/pb_ds/assoc_container.hpp>
// #include <ext/pb_ds/tree_policy.hpp>
// #include <ext/pb_ds/priority_queue.hpp>
//#pragma GCC optimize("O3")
//#pragma GCC target("sse4")
//#pragma GCC target("avx2")

#define deb(x) cout << #x << " = " << x << "\n"
#define aint(x) (x).begin(), (x).end()
#define len(x) (int) x.size()
#define lsb(x) (x) & -(x)
#define l(x) (x << 1) + 1
#define r(x) (x << 1) + 2
#define v(x) (int)(x - 'a')

#define xx first
#define yy second
#define mp make_pair
#define pb push_back
#define lb lower_bound
#define ub upper_bound

using namespace std;
// using namespace __gnu_pbds;
typedef long long ll;
typedef long double ld;
typedef pair <int, int> pii;
typedef pair <ld, ld> pld;
typedef pair <int, int> pint;

// template <class T1, class T2 = less <T1>> using pb_heap = __gnu_pbds::priority_queue <T1, T2, binary_heap_tag>;
// template <class T1, class T2 = nuint_type, class T3 = less <T1>> using pb_map = tree <T1, T2, T3, rb_tree_tag, tree_order_statistics_node_update>;
// template <class T1, class T2 = nuint_type, class T3 = hash <T1>> using pb_umap = gp_hash_table <T1, T2, T3>;
template <class T1, class T2, class T3 = hash <T1>> using umap = unordered_map <T1, T2, T3>;
template <class T> using uset = unordered_set <T>;
template <class T> using vec = vector <T>;

const ll infll = numeric_limits <ll>::max() >> 1;
const int inf = numeric_limits <int>::max() >> 1;
const int M = 1 << 14;
const int N = 1001;
const int L = 14;
const int K = 15;
int n, q, cur;

ll dp[M][N];
ll ut[K][N];
ll po[K][N];
int in[K];

string s;

struct Trie {
    int gon[N][L];
    int nxt[N][L];
    int link[N];
    int chr[N];
    int par[N];
    int cnt[N];
    ll sum[N];

    Trie() {
        memset(link, -1, sizeof link);
        memset(gon, -1, sizeof gon);
    }
} trie;

ll calc(int, int);
int link(int);
int go(int, int);

void insert(string &s, int p) {
    int node = 0;
    for(char c: s) {
        int i = v(c);
        if(!trie.nxt[node][i]) {
            trie.nxt[node][i] = ++cur;
            trie.par[cur] = node;
            trie.chr[cur] = i;
        }
        node = trie.nxt[node][i];
    }
    trie.sum[node] += p;
}

int link(int node) {
    if(trie.link[node] == -1) {
        if(!node || !trie.par[node]) {
            trie.link[node] = 0;
        } else {
            trie.link[node] = go(link(trie.par[node]), trie.chr[node]);
            link(trie.link[node]);
            trie.sum[node] += trie.sum[trie.link[node]];
        }
    }
    return trie.link[node];
}

int go(int node, int i) {
    if(trie.gon[node][i] == -1) {
        if(trie.nxt[node][i]) {
            trie.gon[node][i] =
            trie.nxt[node][i];
        } else {
            trie.gon[node][i] = node? go(link(node), i): 0;
        }
    }
    return trie.gon[node][i];
}

void input() {
    cin >> q;
    for(int i = 0; i < q; ++i) {
        int p;
        string t; cin >> t >> p;
        insert(t, p);
    }
    cin >> s;
    for(int i = 0; i <= cur; ++i) {
        link(i);
    }
    n = len(s);
    fill(in, in + K, n);
    for(int i = 0, j = 0; i < n; ++i) {
        if(s[i] == '?') {
            in[j++] = i;
        }
    }
}

int get(int x) {
    return in[14 - __builtin_popcount(x)];
}

void compute(int i, int mask, int node) {
    int indx = __builtin_popcount(mask);
    if(ut[indx][node] != -infll) {
        return;
    }
    ll res = 0;
    int old = node;
    int rig = get(mask);
    for(i++; i < rig; ++i) {
        res += trie.sum[node];
        node = go(node, v(s[i]));
    }
    ut[indx][old] = res;
    po[indx][old] = node;
}

ll calc(int i, int mask, int node) {
    int indx = __builtin_popcount(mask);
    compute(i, mask, node);
    return calc(mask, po[indx][node]) + ut[indx][node];
}

ll calc(int i, int j) {
    int x = get(i);
    if(x == n) {
        return trie.sum[j];
    }
    if(dp[i][j] != -infll) {
        return dp[i][j];
    }
    ll res = -infll;
    for(int c = 0; c < L; ++c) {
        if((i >> c) & 1) {
            res = max(res, calc(x, i ^ (1 << c), go(j, c)));
        }
    }
    return dp[i][j] = res + trie.sum[j];
}
void solve() {
    fill(dp[0], dp[M], -infll);
    fill(ut[0], ut[K], -infll);
    cout << calc(-1, (1 << 14) - 1, 0) << "\n";
}

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    input();
    solve();
}


#include <bits/stdc++.h>
// #include <ext/pb_ds/assoc_container.hpp>
// #include <ext/pb_ds/tree_policy.hpp>
// #include <ext/pb_ds/priority_queue.hpp>
//#pragma GCC optimize("O3")
//#pragma GCC target("sse4")
//#pragma GCC target("avx2")

#define deb(x) cout << #x << " = " << x << "\n"
#define all(x) (x).begin(), (x).end()
#define len(x) (ll) x.size()
#define lsb(x) (x) & -(x)
#define l(x) (x << 1) + 1
#define r(x) (x << 1) + 2
#define v(x) (ll)(x - 'a')

#define xx first
#define yy second
#define mp make_pair
#define pb push_back
#define lb lower_bound
#define ub upper_bound

using namespace std;
// using namespace __gnu_pbds;
typedef long long ll;
typedef long double ld;
typedef pair <ll, ll> pii;
typedef pair <ld, ld> pld;
typedef pair <ll, ll> pll;

// template <class T1, class T2 = less <T1>> using pb_heap = __gnu_pbds::priority_queue <T1, T2, binary_heap_tag>;
// template <class T1, class T2 = null_type, class T3 = less <T1>> using pb_map = tree <T1, T2, T3, rb_tree_tag, tree_order_statistics_node_update>;
// template <class T1, class T2 = null_type, class T3 = hash <T1>> using pb_umap = gp_hash_table <T1, T2, T3>;
template <class T1, class T2, class T3 = hash <T1>> using umap = unordered_map <T1, T2, T3>;
template <class T> using uset = unordered_set <T>;
template <class T> using vec = vector <T>;

const ll infll = numeric_limits <ll>::max() >> 1;
const ll inf = numeric_limits <ll>::max() >> 1;
const ll M = 1 << 14;
const ll N = 1001;
const ll L = 14;
const ll K = 15;
ll n, q, cur;

ll dp[M][N];
ll ut[M][N];
ll in[K];

string s;

struct Trie {
    ll gon[N][L];
    ll nxt[N][L];
    ll link[N];
    ll chr[N];
    ll par[N];
    ll cnt[N];
    ll sum[N];

    Trie() {
        memset(link, -1, sizeof link);
        memset(gon, -1, sizeof gon);
    }
} trie;

ll calc(ll, ll);
ll link(ll);
ll go(ll, ll);

void insert(string &s, ll p) {
    ll node = 0;
    for(char c: s) {
        ll i = v(c);
        if(!trie.nxt[node][i]) {
            trie.nxt[node][i] = ++cur;
            trie.par[cur] = node;
            trie.chr[cur] = i;
        }
        node = trie.nxt[node][i];
    }
    trie.sum[node] += p;
}

ll link(ll node) {
    if(trie.link[node] == -1) {
        if(!node || !trie.par[node]) {
            trie.link[node] = 0;
        } else {
            trie.link[node] = go(link(trie.par[node]), trie.chr[node]);
            link(trie.link[node]);
            trie.sum[node] += trie.sum[trie.link[node]];
        }
    }
    return trie.link[node];
}

ll go(ll node, ll i) {
    if(trie.gon[node][i] == -1) {
        if(trie.nxt[node][i]) {
            trie.gon[node][i] =
            trie.nxt[node][i];
        } else {
            trie.gon[node][i] = node? go(link(node), i): 0;
        }
    }
    return trie.gon[node][i];
}

void input() {
    cin >> q;
    for(ll i = 0; i < q; ++i) {
        ll p;
        string t; cin >> t >> p;
        insert(t, p);
    }
    cin >> s;
    for(ll i = 0; i <= cur; ++i) {
        link(i);
    }
    n = len(s);
    fill(in, in + K, n);
    for(ll i = 0, j = 0; i < n; ++i) {
        if(s[i] == '?') {
            in[j++] = i;
        }
    }
}

ll get(ll x) {
    return in[14 - __builtin_popcount(x)];
}

ll compute(ll i, ll mask, ll node) {
    if(ut[mask][node] != -inf) {
        return ut[mask][node];
    }
    ll omask = mask;
    ll onode = node;
    ll res = 0;
    for(i++; i < get(mask); ++i) {
        res += trie.sum[node];
        node = go(node, v(s[i]));
    }
    return ut[omask][onode] = calc(mask, node) + res;
}

ll calc(ll i, ll j) {
    ll x = get(i);
    if(x == n) {
        return trie.sum[j];
    }
    if(dp[i][j] != -inf) {
        return dp[i][j];
    }
    ll res = -inf;
    for(ll c = 0; c < L; ++c) {
        if((i >> c) & 1) {
            res = max(res, compute(x, i ^ (1 << c), go(j, c)));
        }
    }
    return dp[i][j] = res + trie.sum[j];
}

void solve() {
    fill(dp[0], dp[M], -inf);
    fill(ut[0], ut[M], -inf);
    cout << compute(-1, (1 << 14) - 1, 0) << "\n";
}

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    input();
    solve();
}


#include <bits/stdc++.h>
// #include <ext/pb_ds/assoc_container.hpp>
// #include <ext/pb_ds/tree_policy.hpp>
// #include <ext/pb_ds/priority_queue.hpp>
//#pragma GCC optimize("O3")
//#pragma GCC target("sse4")
//#pragma GCC target("avx2")

#define deb(x) cout << #x << " = " << x << "\n"
#define aint(x) (x).begin(), (x).end()
#define len(x) (int) x.size()
#define lsb(x) (x) & -(x)
#define l(x) (x << 1) + 1
#define r(x) (x << 1) + 2
#define v(x) (int)(x - 'a')

#define xx first
#define yy second
#define mp make_pair
#define pb push_back
#define lb lower_bound
#define ub upper_bound

using namespace std;
// using namespace __gnu_pbds;
typedef long long ll;
typedef long double ld;
typedef pair <int, int> pii;
typedef pair <ld, ld> pld;
typedef pair <int, int> pint;

// template <class T1, class T2 = less <T1>> using pb_heap = __gnu_pbds::priority_queue <T1, T2, binary_heap_tag>;
// template <class T1, class T2 = nuint_type, class T3 = less <T1>> using pb_map = tree <T1, T2, T3, rb_tree_tag, tree_order_statistics_node_update>;
// template <class T1, class T2 = nuint_type, class T3 = hash <T1>> using pb_umap = gp_hash_table <T1, T2, T3>;
template <class T1, class T2, class T3 = hash <T1>> using umap = unordered_map <T1, T2, T3>;
template <class T> using uset = unordered_set <T>;
template <class T> using vec = vector <T>;

const ll infll = numeric_limits <ll>::max() >> 1;
const int inf = numeric_limits <int>::max() >> 1;
const int M = 1 << 14;
const int N = 1001;
const int L = 14;
const int K = 15;
int n, q, cur;

ll dp[M][N];
ll ut[M][N];
int in[K];

string s;

struct Trie {
    int gon[N][L];
    int nxt[N][L];
    int link[N];
    int chr[N];
    int par[N];
    int cnt[N];
    ll sum[N];

    Trie() {
        memset(link, -1, sizeof link);
        memset(gon, -1, sizeof gon);
    }
} trie;

ll calc(int, int);
int link(int);
int go(int, int);

void insert(string &s, int p) {
    int node = 0;
    for(char c: s) {
        int i = v(c);
        if(!trie.nxt[node][i]) {
            trie.nxt[node][i] = ++cur;
            trie.par[cur] = node;
            trie.chr[cur] = i;
        }
        node = trie.nxt[node][i];
    }
    trie.sum[node] += p;
}

int link(int node) {
    if(trie.link[node] == -1) {
        if(!node || !trie.par[node]) {
            trie.link[node] = 0;
        } else {
            trie.link[node] = go(link(trie.par[node]), trie.chr[node]);
            link(trie.link[node]);
            trie.sum[node] += trie.sum[trie.link[node]];
        }
    }
    return trie.link[node];
}

int go(int node, int i) {
    if(trie.gon[node][i] == -1) {
        if(trie.nxt[node][i]) {
            trie.gon[node][i] =
            trie.nxt[node][i];
        } else {
            trie.gon[node][i] = node? go(link(node), i): 0;
        }
    }
    return trie.gon[node][i];
}

void input() {
    cin >> q;
    for(int i = 0; i < q; ++i) {
        int p;
        string t; cin >> t >> p;
        insert(t, p);
    }
    cin >> s;
    for(int i = 0; i <= cur; ++i) {
        link(i);
    }
    n = len(s);
    fill(in, in + K, n);
    for(int i = 0, j = 0; i < n; ++i) {
        if(s[i] == '?') {
            in[j++] = i;
        }
    }
}

int get(int x) {
    return in[14 - __builtin_popcount(x)];
}

ll compute(int i, int mask, int node) {
    if(ut[mask][node] != -infll) {
        return ut[mask][node];
    }
    int omask = mask;
    int onode = node;
    ll res = 0;
    int rig = get(mask);
    for(i++; i < rig; ++i) {
        res += trie.sum[node];
        node = go(node, v(s[i]));
    }
    return ut[omask][onode] = calc(mask, node) + res;
}

ll calc(int i, int j) {
    int x = get(i);
    if(x == n) {
        return trie.sum[j];
    }
    if(dp[i][j] != -infll) {
        return dp[i][j];
    }
    ll res = -infll;
    for(int c = 0; c < L; ++c) {
        if((i >> c) & 1) {
            res = max(res, compute(x, i ^ (1 << c), go(j, c)));
        }
    }
    return dp[i][j] = res + trie.sum[j];
}

void solve() {
    fill(dp[0], dp[M], -infll);
    fill(ut[0], ut[M], -infll);
    cout << compute(-1, (1 << 14) - 1, 0) << "\n";
}

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    input();
    solve();
}


#include <bits/stdc++.h>
// #include <ext/pb_ds/assoc_container.hpp>
// #include <ext/pb_ds/tree_policy.hpp>
// #include <ext/pb_ds/priority_queue.hpp>
//#pragma GCC optimize("O3")
//#pragma GCC target("sse4")
//#pragma GCC target("avx2")

#define deb(x) cout << #x << " = " << x << "\n"
#define all(x) (x).begin(), (x).end()
#define len(x) (ll) x.size()
#define lsb(x) (x) & -(x)
#define l(x) (x << 1) + 1
#define r(x) (x << 1) + 2
#define v(x) (ll)(x - 'a')

#define xx first
#define yy second
#define mp make_pair
#define pb push_back
#define lb lower_bound
#define ub upper_bound

using namespace std;
// using namespace __gnu_pbds;
typedef long long ll;
typedef long double ld;
typedef pair <ll, ll> pii;
typedef pair <ld, ld> pld;
typedef pair <ll, ll> pll;

// template <class T1, class T2 = less <T1>> using pb_heap = __gnu_pbds::priority_queue <T1, T2, binary_heap_tag>;
// template <class T1, class T2 = null_type, class T3 = less <T1>> using pb_map = tree <T1, T2, T3, rb_tree_tag, tree_order_statistics_node_update>;
// template <class T1, class T2 = null_type, class T3 = hash <T1>> using pb_umap = gp_hash_table <T1, T2, T3>;
template <class T1, class T2, class T3 = hash <T1>> using umap = unordered_map <T1, T2, T3>;
template <class T> using uset = unordered_set <T>;
template <class T> using vec = vector <T>;

const ll infll = numeric_limits <ll>::max() >> 1;
const ll inf = numeric_limits <ll>::max() >> 1;
const ll M = 1 << 14;
const ll N = 1001;
const ll L = 14;
const ll K = 15;
ll n, q, cur;

ll dp[M][N];
ll in[K];

string s;

struct Trie {
    ll gon[N][L];
    ll nxt[N][L];
    ll link[N];
    ll chr[N];
    ll par[N];
    ll cnt[N];
    ll sum[N];

    Trie() {
        memset(link, -1, sizeof link);
        memset(gon, -1, sizeof gon);
    }
} trie;

ll calc(ll, ll);
ll link(ll);
ll go(ll, ll);

void insert(string &s, ll p) {
    ll node = 0;
    for(char c: s) {
        ll i = v(c);
        if(!trie.nxt[node][i]) {
            trie.nxt[node][i] = ++cur;
            trie.par[cur] = node;
            trie.chr[cur] = i;
        }
        node = trie.nxt[node][i];
    }
    trie.sum[node] += p;
}

ll link(ll node) {
    if(trie.link[node] == -1) {
        if(!node || !trie.par[node]) {
            trie.link[node] = 0;
        } else {
            trie.link[node] = go(link(trie.par[node]), trie.chr[node]);
            link(trie.link[node]);
            trie.sum[node] += trie.sum[trie.link[node]];
        }
    }
    return trie.link[node];
}

ll go(ll node, ll i) {
    if(trie.gon[node][i] == -1) {
        if(trie.nxt[node][i]) {
            trie.gon[node][i] =
            trie.nxt[node][i];
        } else {
            trie.gon[node][i] = node? go(link(node), i): 0;
        }
    }
    return trie.gon[node][i];
}

void input() {
    cin >> q;
    for(ll i = 0; i < q; ++i) {
        ll p;
        string t; cin >> t >> p;
        insert(t, p);
    }
    cin >> s;
    for(ll i = 0; i <= cur; ++i) {
        link(i);
    }
    n = len(s);
    fill(in, in + K, n);
    for(ll i = 0, j = 0; i < n; ++i) {
        if(s[i] == '?') {
            in[j++] = i;
        }
    }
}

ll get(ll x) {
    return in[14 - __builtin_popcount(x)];
}

ll compute(ll i, ll mask, ll node) {
    ll res = 0;
    for(i++; i < get(mask); ++i) {
        res += trie.sum[node];
        node = go(node, v(s[i]));
    }
    return calc(mask, node) + res;
}

ll calc(ll i, ll j) {
    ll x = get(i);
    if(x == n) {
        return trie.sum[j];
    }
    if(dp[i][j] != -inf) {
        return dp[i][j];
    }
    ll res = -inf;
    for(ll c = 0; c < L; ++c) {
        if((i >> c) & 1) {
            res = max(res, compute(x, i ^ (1 << c), go(j, c)));
        }
    }
    return dp[i][j] = res + trie.sum[j];
}

void solve() {
    fill(dp[0], dp[M], -inf);
    cout << compute(-1, (1 << 14) - 1, 0) << "\n";
}

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    input();
    solve();
}


#include <bits/stdc++.h>
// #include <ext/pb_ds/assoc_container.hpp>
// #include <ext/pb_ds/tree_policy.hpp>
// #include <ext/pb_ds/priority_queue.hpp>
//#pragma GCC optimize("O3")
//#pragma GCC target("sse4")
//#pragma GCC target("avx2")

#define deb(x) cout << #x << " = " << x << "\n"
#define all(x) (x).begin(), (x).end()
#define len(x) (ll) x.size()
#define lsb(x) (x) & -(x)
#define l(x) (x << 1) + 1
#define r(x) (x << 1) + 2
#define v(x) (ll)(x - 'a')

#define xx first
#define yy second
#define mp make_pair
#define pb push_back
#define lb lower_bound
#define ub upper_bound

using namespace std;
// using namespace __gnu_pbds;
typedef long long ll;
typedef long double ld;
typedef pair <ll, ll> pii;
typedef pair <ld, ld> pld;
typedef pair <ll, ll> pll;

// template <class T1, class T2 = less <T1>> using pb_heap = __gnu_pbds::priority_queue <T1, T2, binary_heap_tag>;
// template <class T1, class T2 = null_type, class T3 = less <T1>> using pb_map = tree <T1, T2, T3, rb_tree_tag, tree_order_statistics_node_update>;
// template <class T1, class T2 = null_type, class T3 = hash <T1>> using pb_umap = gp_hash_table <T1, T2, T3>;
template <class T1, class T2, class T3 = hash <T1>> using umap = unordered_map <T1, T2, T3>;
template <class T> using uset = unordered_set <T>;
template <class T> using vec = vector <T>;

const ll infll = numeric_limits <ll>::max() >> 1;
const ll inf = numeric_limits <ll>::max() >> 1;
const ll M = 1 << 14;
const ll N = 1001;
const ll L = 14;
const ll K = 15;
ll n, q, cur;

ll dp[M][N];
ll ut[M][N];
ll in[K];

string s;

struct Trie {
    ll gon[N][L];
    ll nxt[N][L];
    ll link[N];
    ll chr[N];
    ll par[N];
    ll cnt[N];
    ll sum[N];

    Trie() {
        memset(link, -1, sizeof link);
        memset(gon, -1, sizeof gon);
    }
} trie;

ll calc(ll, ll);
ll link(ll);
ll go(ll, ll);

void insert(string &s, ll p) {
    ll node = 0;
    for(char c: s) {
        ll i = v(c);
        if(!trie.nxt[node][i]) {
            trie.nxt[node][i] = ++cur;
            trie.par[cur] = node;
            trie.chr[cur] = i;
        }
        node = trie.nxt[node][i];
    }
    trie.sum[node] += p;
}

ll link(ll node) {
    if(trie.link[node] == -1) {
        if(!node || !trie.par[node]) {
            trie.link[node] = 0;
        } else {
            trie.link[node] = go(link(trie.par[node]), trie.chr[node]);
            link(trie.link[node]);
            trie.sum[node] += trie.sum[trie.link[node]];
        }
    }
    return trie.link[node];
}

ll go(ll node, ll i) {
    if(trie.gon[node][i] == -1) {
        if(trie.nxt[node][i]) {
            trie.gon[node][i] =
            trie.nxt[node][i];
        } else {
            trie.gon[node][i] = node? go(link(node), i): 0;
        }
    }
    return trie.gon[node][i];
}

void input() {
    cin >> q;
    for(ll i = 0; i < q; ++i) {
        ll p;
        string t; cin >> t >> p;
        insert(t, p);
    }
    cin >> s;
    for(ll i = 0; i <= cur; ++i) {
        link(i);
    }
    n = len(s);
    fill(in, in + K, n);
    for(ll i = 0, j = 0; i < n; ++i) {
        if(s[i] == '?') {
            in[j++] = i;
        }
    }
}

ll get(ll x) {
    return in[14 - __builtin_popcount(x)];
}

ll compute(ll i, ll mask, ll node) {
    if(ut[mask][node] != -inf) {
        return ut[mask][node];
    }
    ll omask = mask;
    ll onode = node;
    ll res = 0;
    ll rig = get(mask);
    for(i++; i < rig; ++i) {
        res += trie.sum[node];
        node = go(node, v(s[i]));
    }
    return ut[omask][onode] = calc(mask, node) + res;
}

ll calc(ll i, ll j) {
    ll x = get(i);
    if(x == n) {
        return trie.sum[j];
    }
    if(dp[i][j] != -inf) {
        return dp[i][j];
    }
    ll res = -inf;
    for(ll c = 0; c < L; ++c) {
        if((i >> c) & 1) {
            res = max(res, compute(x, i ^ (1 << c), go(j, c)));
        }
    }
    return dp[i][j] = res + trie.sum[j];
}

void solve() {
    fill(dp[0], dp[M], -inf);
    fill(ut[0], ut[M], -inf);
    cout << compute(-1, (1 << 14) - 1, 0) << "\n";
}

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    input();
    solve();
}


#include <bits/stdc++.h>
// #include <ext/pb_ds/assoc_container.hpp>
// #include <ext/pb_ds/tree_policy.hpp>
// #include <ext/pb_ds/priority_queue.hpp>
//#pragma GCC optimize("O3")
//#pragma GCC target("sse4")
//#pragma GCC target("avx2")

#define deb(x) cout << #x << " = " << x << "\n"
#define all(x) (x).begin(), (x).end()
#define len(x) (ll) x.size()
#define lsb(x) (x) & -(x)
#define l(x) (x << 1) + 1
#define r(x) (x << 1) + 2
#define v(x) (ll)(x - 'a')

#define xx first
#define yy second
#define mp make_pair
#define pb push_back
#define lb lower_bound
#define ub upper_bound

using namespace std;
// using namespace __gnu_pbds;
typedef long long ll;
typedef long double ld;
typedef pair <ll, ll> pii;
typedef pair <ld, ld> pld;
typedef pair <ll, ll> pll;

// template <class T1, class T2 = less <T1>> using pb_heap = __gnu_pbds::priority_queue <T1, T2, binary_heap_tag>;
// template <class T1, class T2 = null_type, class T3 = less <T1>> using pb_map = tree <T1, T2, T3, rb_tree_tag, tree_order_statistics_node_update>;
// template <class T1, class T2 = null_type, class T3 = hash <T1>> using pb_umap = gp_hash_table <T1, T2, T3>;
template <class T1, class T2, class T3 = hash <T1>> using umap = unordered_map <T1, T2, T3>;
template <class T> using uset = unordered_set <T>;
template <class T> using vec = vector <T>;

const ll infll = numeric_limits <ll>::max() >> 1;
const ll inf = numeric_limits <ll>::max() >> 1;
const ll M = 1 << 14;
const ll N = 1001;
const ll L = 14;
const ll K = 15;
ll n, q, cur;

ll dp[M][N];
ll ut[M][N];
ll in[K];

string s;

struct Trie {
    ll gon[N][L];
    ll nxt[N][L];
    ll link[N];
    ll chr[N];
    ll par[N];
    ll cnt[N];
    ll sum[N];

    Trie() {
        memset(link, -1, sizeof link);
        memset(gon, -1, sizeof gon);
    }
} trie;

ll calc(ll, ll);
ll link(ll);
ll go(ll, ll);

void insert(string &s, ll p) {
    ll node = 0;
    for(char c: s) {
        ll i = v(c);
        if(!trie.nxt[node][i]) {
            trie.nxt[node][i] = ++cur;
            trie.par[cur] = node;
            trie.chr[cur] = i;
        }
        node = trie.nxt[node][i];
    }
    trie.sum[node] += p;
}

ll link(ll node) {
    if(trie.link[node] == -1) {
        if(!node || !trie.par[node]) {
            trie.link[node] = 0;
        } else {
            trie.link[node] = go(link(trie.par[node]), trie.chr[node]);
            link(trie.link[node]);
            trie.sum[node] += trie.sum[trie.link[node]];
        }
    }
    return trie.link[node];
}

ll go(ll node, ll i) {
    if(trie.gon[node][i] == -1) {
        if(trie.nxt[node][i]) {
            trie.gon[node][i] =
            trie.nxt[node][i];
        } else {
            trie.gon[node][i] = node? go(link(node), i): 0;
        }
    }
    return trie.gon[node][i];
}

void input() {
    cin >> q;
    for(ll i = 0; i < q; ++i) {
        ll p;
        string t; cin >> t >> p;
        insert(t, p);
    }
    cin >> s;
    for(ll i = 0; i <= cur; ++i) {
        link(i);
    }
    n = len(s);
    fill(in, in + K, n);
    for(ll i = 0, j = 0; i < n; ++i) {
        if(s[i] == '?') {
            in[j++] = i;
        }
    }
}

ll get(ll x) {
    return in[14 - __builtin_popcount(x)];
}

ll compute(ll i, ll mask, ll node) {
    ll res = 0;
    if(ut[mask][node] != -inf) {
        return ut[mask][node];
    }
    for(i++; i < get(mask); ++i) {
        res += trie.sum[node];
        node = go(node, v(s[i]));
    }
    return ut[mask][node] = calc(mask, node) + res;
}

ll calc(ll i, ll j) {
    ll x = get(i);
    if(x == n) {
        return trie.sum[j];
    }
    if(dp[i][j] != -inf) {
        return dp[i][j];
    }
    ll res = -inf;
    for(ll c = 0; c < L; ++c) {
        if((i >> c) & 1) {
            res = max(res, compute(x, i ^ (1 << c), go(j, c)));
        }
    }
    return dp[i][j] = res + trie.sum[j];
}

void solve() {
    fill(dp[0], dp[M], -inf);
    fill(ut[0], ut[M], -inf);
    cout << compute(-1, (1 << 14) - 1, 0) << "\n";
}

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    input();
    solve();
}


#include <bits/stdc++.h>

using namespace std;
using ll = long long;

const int N = 1003, A = 14;

int to[N][A];
int ne = 2, fail[N], term[N];
void add_string(const char *str, int id){
    int p = 1;
    for(int i = 0; str[i]; i++){
        int ch = int(str[i] - 'a');
        if(!to[p][ch]) to[p][ch] = ne++;
        p = to[p][ch];
    }
    term[p] += id;
}
void init() {
    for(int i = 0; i < ne; i++) fail[i] = 1;
    queue<int> q; q.push(1);
    int u, v;
    while(!q.empty()){
        u = q.front(); q.pop();
        for(int i = 0; i < A; i++){
            if(to[u][i]) {
                v = to[u][i]; q.push(v);
                if(u != 1){
                    fail[v] = to[ fail[u] ][i];
                    term[v] += term[ fail[v] ];
                }
            }
            else if(u != 1) to[u][i] = to[ fail[u] ][i];
            else to[u][i] = 1;
        }
    }
}

const int M = 400005;
int k;
char s[M];
int t = 0;
int n;

ll dp[2][N][1 << 14];
int vis[2][N][1 << 14];

int nx[16][N];
ll val[16][N];

vector<int> by_cnt[15];

ll f(int i, int st, int mask) {
	if(i == t) return 0;
	ll &ans = dp[i][st][mask];
	if(vis[i][st][mask]) return ans;
	vis[i][st][mask] = 1;
	ans = -1e18;
	for(int c = 0; c < 14; c++) if(!(mask >> c & 1)) {
		int st2 = to[st][c];
		ll cand = term[st2];
		cand += f(i + 1, nx[i][st2], mask + (1 << c));
		cand += val[i][st2];
		ans = max(ans, cand);
	}
	return ans;
}

void relax(int i, int st, int mask) {
	if(i == t) return;
	ll &ans = dp[i % 2][st][mask];
	ans = -1e18;
	for(int c = 0; c < 14; c++) if(!(mask >> c & 1)) {
		int st2 = to[st][c];
		ll cand = term[st2];
		cand += dp[(i + 1) % 2][ nx[i][st2] ][ mask + (1 << c) ];
		cand += val[i][st2];
		ans = max(ans, cand);
	}
}

int main() {

	scanf("%d", &k);

	for(int i = 0; i < k; i++) {
		int sval;
		scanf(" %s %d", s, &sval);
		add_string(s, sval);
	}
	init();

	scanf(" %s", s);
	n = (int)strlen(s);

	for(int j = 1; j < ne; j++) nx[t][j] = j;
	for(int i = 0; i < n; i++) {
		if(s[i] == '?') {
			t++;
			for(int j = 1; j < ne; j++) nx[t][j] = j;
		}
		else {
			int c = int(s[i] - 'a');
			for(int j = 1; j < ne; j++) {
				nx[t][j] = to[ nx[t][j] ][c];
				val[t][j] += term[ nx[t][j] ];
			}
		}
	}
	t++;
	assert(t < 16);

	for(int i = 0; i < (1 << 14); i++) {
		by_cnt[__builtin_popcount(i)].push_back(i);
	}

	for(int i = t - 1; i > 0; i--) {
		for(int st = 1; st < ne; st++) {
			for(int mask : by_cnt[i - 1]) {
				relax(i, st, mask);
			}
		}
	}

	printf("%lld\n", dp[1][ nx[0][1] ][0] + val[0][1]);
}


#include <bits/stdc++.h>

using namespace std;
using ll = long long;

const int N = 1003, A = 14;

int to[N][A];
int ne = 2, fail[N], term[N];
void add_string(const char *str, int id){
    int p = 1;
    for(int i = 0; str[i]; i++){
        int ch = int(str[i] - 'a');
        if(!to[p][ch]) to[p][ch] = ne++;
        p = to[p][ch];
    }
    term[p] += id;
}
void init() {
    for(int i = 0; i < ne; i++) fail[i] = 1;
    queue<int> q; q.push(1);
    int u, v;
    while(!q.empty()){
        u = q.front(); q.pop();
        for(int i = 0; i < A; i++){
            if(to[u][i]) {
                v = to[u][i]; q.push(v);
                if(u != 1){
                    fail[v] = to[ fail[u] ][i];
                    term[v] += term[ fail[v] ];
                }
            }
            else if(u != 1) to[u][i] = to[ fail[u] ][i];
            else to[u][i] = 1;
        }
    }
}

const int M = 400005;
int k;
char s[M];
int t = 0;
int n;

ll dp[16][N][1 << 14];
int vis[16][N][1 << 14];

int nx[16][N];
ll val[16][N];

vector<int> by_cnt[15];

ll f(int i, int st, int mask) {
	if(i == t) return 0;
	ll &ans = dp[i][st][mask];
	if(vis[i][st][mask]) return ans;
	vis[i][st][mask] = 1;
	ans = -1e18;
	for(int c = 0; c < 14; c++) if(!(mask >> c & 1)) {
		int st2 = to[st][c];
		ll cand = term[st2];
		cand += f(i + 1, nx[i][st2], mask + (1 << c));
		cand += val[i][st2];
		ans = max(ans, cand);
	}
	return ans;
}

int main() {

	scanf("%d", &k);

	for(int i = 0; i < k; i++) {
		int sval;
		scanf(" %s %d", s, &sval);
		add_string(s, sval);
	}
	init();

	scanf(" %s", s);
	n = (int)strlen(s);

	for(int j = 1; j < ne; j++) nx[t][j] = j;
	for(int i = 0; i < n; i++) {
		if(s[i] == '?') {
			t++;
			for(int j = 1; j < ne; j++) nx[t][j] = j;
		}
		else {
			int c = int(s[i] - 'a');
			for(int j = 1; j < ne; j++) {
				nx[t][j] = to[ nx[t][j] ][c];
				val[t][j] += term[ nx[t][j] ];
			}
		}
	}
	t++;
	assert(t < 16);

	printf("%lld\n", f(1, nx[0][1], 0) + val[0][1]);
}


#include <bits/stdc++.h>

using namespace std;
using ll = long long;

const int N = 103, A = 14;

int to[N][A];
int ne = 2, fail[N], term[N];
void add_string(const char *str, int id){
    int p = 1;
    for(int i = 0; str[i]; i++){
        int ch = int(str[i] - 'a');
        if(!to[p][ch]) to[p][ch] = ne++;
        p = to[p][ch];
    }
    term[p] += id;
}
void init() {
    for(int i = 0; i < ne; i++) fail[i] = 1;
    queue<int> q; q.push(1);
    int u, v;
    while(!q.empty()){
        u = q.front(); q.pop();
        for(int i = 0; i < A; i++){
            if(to[u][i]) {
                v = to[u][i]; q.push(v);
                if(u != 1){
                    fail[v] = to[ fail[u] ][i];
                    term[v] += term[ fail[v] ];
                }
            }
            else if(u != 1) to[u][i] = to[ fail[u] ][i];
            else to[u][i] = 1;
        }
    }
}

const int M = 400005;
int k;
char s[M];
int t = 0;
int n;

ll dp[16][N][1 << 14];
int vis[16][N][1 << 14];

int nx[16][N];
ll val[16][N];

vector<int> by_cnt[15];

ll f(int i, int st, int mask) {
	if(i == t) return 0;
	ll &ans = dp[i][st][mask];
	if(vis[i][st][mask]) return ans;
	vis[i][st][mask] = 1;
	ans = -1e18;
	for(int c = 0; c < 14; c++) if(!(mask >> c & 1)) {
		int st2 = to[st][c];
		ll cand = term[st2];
		cand += f(i + 1, nx[i][st2], mask + (1 << c));
		cand += val[i][st2];
		ans = max(ans, cand);
	}
	return ans;
}

int main() {

	scanf("%d", &k);

	for(int i = 0; i < k; i++) {
		int sval;
		scanf(" %s %d", s, &sval);
		add_string(s, sval);
	}
	init();

	scanf(" %s", s);
	n = (int)strlen(s);

	for(int j = 1; j < ne; j++) nx[t][j] = j;
	for(int i = 0; i < n; i++) {
		if(s[i] == '?') {
			t++;
			for(int j = 1; j < ne; j++) nx[t][j] = j;
		}
		else {
			int c = int(s[i] - 'a');
			for(int j = 1; j < ne; j++) {
				nx[t][j] = to[ nx[t][j] ][c];
				val[t][j] += term[ nx[t][j] ];
			}
		}
	}
	t++;
	assert(t < 16);

	for(int i = 0; i < (1 << 14); i++) {
		by_cnt[__builtin_popcount(i)].push_back(i);
	}

	printf("%lld\n", f(1, nx[0][1], 0) + val[0][1]);
}


// UPD: oops. Isn't the limit sum(t)+sum(c) <= INT_MAX completely arbitrary?
//
//
// >.< I don't have a prepped template for AhoCorasick and looking for a working implementation
// is usually harder than reimplement from scratch...
//
// upd: took too long to debug the extraneous/missing std::swap(value, oldValue).
// I should've done static debug before dynamic debug...
//
// sorry for the warnings.

#ifndef LOCAL
#define NDEBUG 1
#endif
#include<bits/stdc++.h>

struct AhoCorasick{
	using NodeIndex=int;
	struct Node{
		NodeIndex sfxLink;
		int64_t cost; // minimum len of a dict word reachable via sfxLink, or 0
		std::array<NodeIndex,26> child;
	};
	std::vector<Node> data;

	bool constructed=false;

	AhoCorasick(){
		newNode(); // root
	}

	NodeIndex newNode(){
		assert(!constructed);
		data.push_back({-1,0});
		for(auto & x: data.back().child) x=-1;
		assert(NodeIndex(data.size()-1)>=0);
		return NodeIndex(data.size()-1);
	}

	NodeIndex child(NodeIndex node,int letter){
		assert(0<=letter&&letter<26);
		assert(0<=node);
		if(data[node].child[letter]<0){
			assert(!constructed);
			NodeIndex z=newNode();
			assert(z>=0);
			data[node].child[letter]=z;
			assert(data[node].child[letter]>=0);
		}else{
			assert(data[node].child[letter]>=0);
		}
		assert(data[node].child[letter]>=0);
		return data[node].child[letter];
	}

	NodeIndex next(int node, int direction) const{
		return data[node].child[direction];
	}

	void build(){
		assert(!constructed);
		constructed=true;

		std::queue<NodeIndex> q;
		q.push(0);
		while(!q.empty()){
			NodeIndex node=q.front();q.pop();
			assert(node==0||data[node].sfxLink>=0);
			for(int letter=0;letter<26;++letter){
				NodeIndex& child_=data[node].child[letter];
				if(child_<0){
					child_=node==0 ? 0: data[data[node].sfxLink].child[letter];
					assert(child_>=0);
					continue;
				}

				// construct sfxLink for child_
				NodeIndex z=data[node].sfxLink;
				while(z>=0&&data[z].child[letter]<0)
					z=data[z].sfxLink;
				if(z<0)
					data[child_].sfxLink=0;
				else
					data[child_].sfxLink=data[z].child[letter];

				// construct cost for child_
				data[child_].cost+=data[data[child_].sfxLink].cost;
				q.push(child_);
			}
		}
	}

};

int main(){
	std::ios::sync_with_stdio(0);std::cin.tie(0);
	int numString; std::cin>>numString;

	AhoCorasick tree;
	std::string string;
	for(int _=0;_<numString;++_) {
		int cost;
		std::cin>>string>>cost;
		assert(std::abs(cost)<=1000000);

		int node=0;
		for(auto ch: string) node=tree.child(node, ch-'a');
		tree.data[node].cost+=cost;
	}
	tree.build();
	assert(tree.data[0].cost==0);

	struct item{
		int node; int64_t maximumValue;
		bool operator<(item other) const{ return maximumValue<other.maximumValue; }
#if not NDEBUG
		bool operator==(item other) const{ return std::tie(node, maximumValue)==
			std::tie(other.node, other.maximumValue); }
#endif
	};
	std::vector<item> value(1<<14, {-1, INT_MIN}); // [mask of used characters]
	value[0]={0, 0};

	std::vector<item> pending(tree.data.size());
	auto const resetPending=[&]{
		assert(pending.size()==tree.data.size());
		for(int node=0;node<(int)pending.size();++node) pending[node]={node, 0};
	};
	resetPending();

	std::cin>>string;
	std::vector<item> newValue; // temporary, to avoid memory reallocation

	auto const resetNewValue=[&]{
		newValue.assign(1<<14, {-1, INT_MIN});
	};

#if not NDEBUG
	auto const nonePending=[&]{
		auto old=pending;
		resetPending();
		std::swap(pending, old);
		return old==pending;
	};
#endif

	auto const applyPending=[&]{
		bool nonePending_;
		assert((nonePending_=nonePending(), true));
		resetNewValue();

		for(auto mask=value.size(); mask--;){
			auto const [node, cost]=value[mask];
			if(node<0) { assert(cost==INT_MIN); continue; }

			auto& it=newValue[mask];
			auto const [newNode, addedCost]=pending[node];
			assert(newNode>=0);
			it=std::max(it, {newNode, cost+addedCost});
		}

		assert([&]{
				auto const x=std::max_element(begin(value),end(value))->maximumValue;
				auto const y=std::max_element(begin(newValue),end(newValue))->maximumValue;
				//assert(x<=y);
				return true;
		}());
		std::swap(value, newValue);

		assert(nonePending_ ? value==newValue: true);

		resetPending();
	};

	for(auto ch: string){
		if(ch=='?'){
			applyPending();

			resetNewValue();
			for(auto mask=value.size(); mask--;){
				auto const [node, cost]=value[mask];
				if(node<0) continue;
				for(int ch=0;ch<14;++ch) if(not(mask>>ch&1)){
					auto& it=newValue[mask|1<<ch];
					auto const newNode=tree.next(node, ch);
					it=std::max(it, {newNode, cost+tree.data[newNode].cost});
				}
			}
			std::swap(value, newValue);
		}else{
			assert((applyPending(), true));

			ch-='a';

			for(auto &[node, value]: pending){
				node=tree.next(node, ch);
				value+=tree.data[node].cost;
			}

			/*
			resetNewValue();
			for(auto mask=value.size(); mask--;){
				auto const [node, cost]=value[mask];
				if(node<0) continue;
				auto const newNode=tree.next(node, ch);
				newValue[mask]=std::max(newValue[mask], {newNode, cost+tree.data[newNode].cost});
			}
			*/

			assert((applyPending(), true));
		}

	}
	applyPending();

	std::cout<<std::max_element(begin(value),end(value))->maximumValue<<'\n';
}


// wrong algorithm... now fixed. :(
// but taking about half an hour to find and fix the INT_MIN bug is still terribly slow.

// UPD: more constant optimization. Might still TLE.
// should look for algorithmic optimization instead...

#ifndef LOCAL
#define NDEBUG 1
#endif
#include<bits/stdc++.h>

int const numChar=14;

struct AhoCorasick{
	using NodeIndex=int;
	struct Node{
		NodeIndex sfxLink;
		int64_t cost; // minimum len of a dict word reachable via sfxLink, or 0
		std::array<NodeIndex,numChar> child;
	};
	std::vector<Node> data;

	bool constructed=false;

	AhoCorasick(){
		newNode(); // root
	}

	NodeIndex newNode(){
		assert(!constructed);
		data.push_back({-1,0});
		for(auto & x: data.back().child) x=-1;
		assert(NodeIndex(data.size()-1)>=0);
		return NodeIndex(data.size()-1);
	}

	NodeIndex child(NodeIndex node,int letter){
		assert(0<=letter&&letter<numChar);
		assert(0<=node);
		if(data[node].child[letter]<0){
			assert(!constructed);
			NodeIndex z=newNode();
			assert(z>=0);
			data[node].child[letter]=z;
			assert(data[node].child[letter]>=0);
		}else{
			assert(data[node].child[letter]>=0);
		}
		assert(data[node].child[letter]>=0);
		return data[node].child[letter];
	}

	NodeIndex next(int node, int direction) const{
		return data[node].child[direction];
	}

	void build(){
		assert(!constructed);
		constructed=true;

		std::queue<NodeIndex> q;
		q.push(0);
		while(!q.empty()){
			NodeIndex node=q.front();q.pop();
			assert(node==0||data[node].sfxLink>=0);
			for(int letter=0;letter<numChar;++letter){
				NodeIndex& child_=data[node].child[letter];
				if(child_<0){
					child_=node==0 ? 0: data[data[node].sfxLink].child[letter];
					assert(child_>=0);
					continue;
				}

				// construct sfxLink for child_
				NodeIndex z=data[node].sfxLink;
				while(z>=0&&data[z].child[letter]<0)
					z=data[z].sfxLink;
				if(z<0)
					data[child_].sfxLink=0;
				else
					data[child_].sfxLink=data[z].child[letter];

				// construct cost for child_
				data[child_].cost+=data[data[child_].sfxLink].cost;
				q.push(child_);
			}
		}
	}

};

int main(){
	std::ios::sync_with_stdio(0);std::cin.tie(0);
	int numString; std::cin>>numString;

	AhoCorasick tree;
	std::string string;
	for(int _=0;_<numString;++_) {
		int cost;
		std::cin>>string>>cost;
		assert(std::abs(cost)<=1000000);

		int node=0;
		for(auto ch: string) node=tree.child(node, ch-'a');
		tree.data[node].cost+=cost;
	}
	tree.build();
	assert(tree.data[0].cost==0);

	int const numMask=1<<numChar;
	std::vector<std::vector<int64_t>> value(numMask, std::vector<int64_t>(tree.data.size(), INT64_MIN));
	// [mask of used characters][node] = maximum
	value[0][0]=0;

	struct item{
		int node; int64_t addedValue;
	};
	std::vector<item> pending(tree.data.size());

	auto const resetPending=[&]{
		assert(pending.size()==tree.data.size());
		for(int node=0;node<(int)pending.size();++node) pending[node]={node, 0};
	};
	resetPending();

	auto const applyPending=[&]{
		std::vector<int64_t> newValue(tree.data.size());

		for(auto mask=numMask; mask--;){
			// note: is it more memory efficient to swap node and mask?

			std::fill(begin(newValue),end(newValue), INT64_MIN);
			for(int node=0;node<(int)tree.data.size();++node) {
				auto const cost=value[mask][node];
				if(cost==INT64_MIN) continue;

				auto const [newNode, addedCost]=pending[node];
				assert(newNode>=0);
				newValue[newNode]=std::max(newValue[newNode], cost+addedCost);
			}
			std::swap(value[mask], newValue);
		}

		resetPending();
	};

	auto const addPendingChar=[&](int direction){
		for(auto &[node, value]: pending){
			node=tree.next(node, direction);
			value+=tree.data[node].cost;
		}
	};

	std::cin>>string;
	for(auto ch: string){
		if(ch=='?'){
			applyPending();

			for(auto mask=numMask; mask--;){
				// now value at mask>mask is new, value at mask<=mask is old
				for(int node=0;node<(int)tree.data.size();++node) {
					auto const cost=value[mask][node]; if(cost==INT64_MIN) continue;
					for(auto tmp=(numMask-1) xor mask; tmp; tmp-=tmp&-tmp) {
						auto const ch=__builtin_ctz(tmp);
						auto const newNode=tree.next(node, ch);
						auto& it=value[mask|1<<ch][newNode];
						it=std::max(it, cost+tree.data[newNode].cost);
					}
				}
				std::fill(begin(value[mask]),end(value[mask]), INT64_MIN);
				// now value at mask>=mask is new, value at mask<mask is old
			}
		}else{
			addPendingChar(ch-'a');
		}
	}
	applyPending();

	std::cout<<std::accumulate(begin(value),end(value), INT64_MIN,[&](int64_t value, auto const& it){
		return std::max(value, *std::max_element(begin(it),end(it)));
	})
	<<'\n';
}


// :/

#ifndef LOCAL
#define NDEBUG 1
#endif
#include<bits/stdc++.h>

struct AhoCorasick{
	using NodeIndex=int;
	struct Node{
		NodeIndex sfxLink;
		int64_t cost; // minimum len of a dict word reachable via sfxLink, or 0
		std::array<NodeIndex,14> child;
	};
	std::vector<Node> data;

	bool constructed=false;

	AhoCorasick(){
		newNode(); // root
	}

	NodeIndex newNode(){
		assert(!constructed);
		data.push_back({-1,0});
		for(auto & x: data.back().child) x=-1;
		assert(NodeIndex(data.size()-1)>=0);
		return NodeIndex(data.size()-1);
	}

	NodeIndex child(NodeIndex node,int letter){
		assert(0<=letter&&letter<14);
		assert(0<=node);
		if(data[node].child[letter]<0){
			assert(!constructed);
			NodeIndex z=newNode();
			assert(z>=0);
			data[node].child[letter]=z;
			assert(data[node].child[letter]>=0);
		}else{
			assert(data[node].child[letter]>=0);
		}
		assert(data[node].child[letter]>=0);
		return data[node].child[letter];
	}

	NodeIndex next(int node, int direction) const{
		return data[node].child[direction];
	}

	void build(){
		assert(!constructed);
		constructed=true;

		std::queue<NodeIndex> q;
		q.push(0);
		while(!q.empty()){
			NodeIndex node=q.front();q.pop();
			assert(node==0||data[node].sfxLink>=0);
			for(int letter=0;letter<14;++letter){
				NodeIndex& child_=data[node].child[letter];
				if(child_<0){
					child_=node==0 ? 0: data[data[node].sfxLink].child[letter];
					assert(child_>=0);
					continue;
				}

				// construct sfxLink for child_
				NodeIndex z=data[node].sfxLink;
				while(z>=0&&data[z].child[letter]<0)
					z=data[z].sfxLink;
				if(z<0)
					data[child_].sfxLink=0;
				else
					data[child_].sfxLink=data[z].child[letter];

				// construct cost for child_
				data[child_].cost+=data[data[child_].sfxLink].cost;
				q.push(child_);
			}
		}
	}

};

int main(){
	std::ios::sync_with_stdio(0);std::cin.tie(0);
	int numString; std::cin>>numString;

	AhoCorasick tree;
	std::string string;
	for(int _=0;_<numString;++_) {
		int cost;
		std::cin>>string>>cost;
		assert(std::abs(cost)<=1000000);

		int node=0;
		for(auto ch: string) node=tree.child(node, ch-'a');
		tree.data[node].cost+=cost;
	}
	tree.build();
	assert(tree.data[0].cost==0);

	struct item{
		int node; int64_t maximumValue;
		bool operator<(item other) const{ return maximumValue<other.maximumValue; }
#if not NDEBUG
		bool operator==(item other) const{ return std::tie(node, maximumValue)==
			std::tie(other.node, other.maximumValue); }
#endif
	};
	std::vector<item> value(1<<14, {-1, INT64_MIN}); // [mask of used characters]
	value[0]={0, 0};

	std::vector<item> pending(tree.data.size());
	auto const resetPending=[&]{
		assert(pending.size()==tree.data.size());
		for(int node=0;node<(int)pending.size();++node) pending[node]={node, 0};
	};
	resetPending();

	std::cin>>string;
	std::vector<item> newValue; // temporary, to avoid memory reallocation

	auto const resetNewValue=[&]{
		newValue.assign(1<<14, {-1, INT_MIN});
	};

	auto const applyPending=[&]{
		resetNewValue();

		for(auto mask=value.size(); mask--;){
			auto const [node, cost]=value[mask];
			if(node<0) { assert(cost==INT_MIN); continue; }

			auto& it=newValue[mask];
			auto const [newNode, addedCost]=pending[node];
			assert(newNode>=0);
			it=std::max(it, {newNode, cost+addedCost});
		}

		std::swap(value, newValue);

		resetPending();
	};

	for(auto ch: string){
		if(ch=='?'){
			applyPending();

			resetNewValue();
			for(auto mask=value.size(); mask--;){
				auto const [node, cost]=value[mask];
				if(node<0) continue;
				for(int ch=0;ch<14;++ch) if(not(mask>>ch&1)){
					auto& it=newValue[mask|1<<ch];
					auto const newNode=tree.next(node, ch);
					it=std::max(it, {newNode, cost+tree.data[newNode].cost});
				}
			}
			std::swap(value, newValue);
		}else{

			ch=char(ch-'a');

			for(auto &[node, value]: pending){
				node=tree.next(node, ch);
				value+=tree.data[node].cost;
			}

			/*
			resetNewValue();
			for(auto mask=value.size(); mask--;){
				auto const [node, cost]=value[mask];
				if(node<0) continue;
				auto const newNode=tree.next(node, ch);
				newValue[mask]=std::max(newValue[mask], {newNode, cost+tree.data[newNode].cost});
			}
			*/

		}

	}
	applyPending();

	std::cout<<std::max_element(begin(value),end(value))->maximumValue<<'\n';
}


// wrong algorithm... now fixed. :(
// but taking about half an hour to find and fix the INT_MIN bug is still terribly slow.

#ifndef LOCAL
#define NDEBUG 1
#endif
#include<bits/stdc++.h>

int const numChar=14;

struct AhoCorasick{
	using NodeIndex=int;
	struct Node{
		NodeIndex sfxLink;
		int64_t cost; // minimum len of a dict word reachable via sfxLink, or 0
		std::array<NodeIndex,numChar> child;
	};
	std::vector<Node> data;

	bool constructed=false;

	AhoCorasick(){
		newNode(); // root
	}

	NodeIndex newNode(){
		assert(!constructed);
		data.push_back({-1,0});
		for(auto & x: data.back().child) x=-1;
		assert(NodeIndex(data.size()-1)>=0);
		return NodeIndex(data.size()-1);
	}

	NodeIndex child(NodeIndex node,int letter){
		assert(0<=letter&&letter<numChar);
		assert(0<=node);
		if(data[node].child[letter]<0){
			assert(!constructed);
			NodeIndex z=newNode();
			assert(z>=0);
			data[node].child[letter]=z;
			assert(data[node].child[letter]>=0);
		}else{
			assert(data[node].child[letter]>=0);
		}
		assert(data[node].child[letter]>=0);
		return data[node].child[letter];
	}

	NodeIndex next(int node, int direction) const{
		return data[node].child[direction];
	}

	void build(){
		assert(!constructed);
		constructed=true;

		std::queue<NodeIndex> q;
		q.push(0);
		while(!q.empty()){
			NodeIndex node=q.front();q.pop();
			assert(node==0||data[node].sfxLink>=0);
			for(int letter=0;letter<numChar;++letter){
				NodeIndex& child_=data[node].child[letter];
				if(child_<0){
					child_=node==0 ? 0: data[data[node].sfxLink].child[letter];
					assert(child_>=0);
					continue;
				}

				// construct sfxLink for child_
				NodeIndex z=data[node].sfxLink;
				while(z>=0&&data[z].child[letter]<0)
					z=data[z].sfxLink;
				if(z<0)
					data[child_].sfxLink=0;
				else
					data[child_].sfxLink=data[z].child[letter];

				// construct cost for child_
				data[child_].cost+=data[data[child_].sfxLink].cost;
				q.push(child_);
			}
		}
	}

};

int main(){
	std::ios::sync_with_stdio(0);std::cin.tie(0);
	int numString; std::cin>>numString;

	AhoCorasick tree;
	std::string string;
	for(int _=0;_<numString;++_) {
		int cost;
		std::cin>>string>>cost;
		assert(std::abs(cost)<=1000000);

		int node=0;
		for(auto ch: string) node=tree.child(node, ch-'a');
		tree.data[node].cost+=cost;
	}
	tree.build();
	assert(tree.data[0].cost==0);

	int const numMask=1<<numChar;
	std::vector<std::array<int64_t, numMask>> value(tree.data.size());
	// [node][mask of used characters] = maximum
	for(auto & it: value) std::fill(begin(it),end(it),
			std::numeric_limits<std::decay_t<decltype(it[0])>>::min());
	value[0][0]=0;

	struct item{
		int node; int64_t addedValue;
	};
	std::vector<item> pending(tree.data.size());

	auto const resetPending=[&]{
		assert(pending.size()==tree.data.size());
		for(int node=0;node<(int)pending.size();++node) pending[node]={node, 0};
	};
	resetPending();

	auto const applyPending=[&]{
		std::vector<int64_t> newValue(tree.data.size());

		for(auto mask=numMask; mask--;){
			// note: is it more memory efficient to swap node and mask?

			std::fill(begin(newValue),end(newValue), INT64_MIN);
			for(int node=0;node<(int)tree.data.size();++node) {
				auto const cost=value[node][mask];
				if(cost==INT64_MIN) continue;

				auto const [newNode, addedCost]=pending[node];
				assert(newNode>=0);
				newValue[newNode]=std::max(newValue[newNode], cost+addedCost);
			}
			for(int node=0;node<(int)tree.data.size();++node)
				value[node][mask]=newValue[node];
		}

		resetPending();
	};

	auto const addPendingChar=[&](int direction){
		for(auto &[node, value]: pending){
			node=tree.next(node, direction);
			value+=tree.data[node].cost;
		}
	};

	std::cin>>string;
	for(auto ch: string){
		if(ch=='?'){
			applyPending();

			for(auto mask=numMask; mask--;){
				// now value at mask>mask is new, value at mask<=mask is old
				for(int node=0;node<(int)tree.data.size();++node) {
					auto const cost=value[node][mask]; if(cost==std::numeric_limits<decltype(cost)>::min()) continue;
					for(int ch=0;ch<numChar;++ch) if(not(mask>>ch&1)){
						auto const newNode=tree.next(node, ch);
						auto& it=value[newNode][mask|1<<ch];
						it=std::max(it, cost+tree.data[newNode].cost);
					}
				}
				for(int node=0;node<(int)tree.data.size();++node) {
					value[node][mask]=std::numeric_limits<std::decay_t<decltype(value[node][mask])>>::min();
				}
				// now value at mask>=mask is new, value at mask<mask is old
			}
		}else{
			addPendingChar(ch-'a');
		}
	}
	applyPending();

	std::cout<<std::accumulate(begin(value),end(value), INT64_MIN,[&](int64_t value, auto const& it){
		return std::max(value, *std::max_element(begin(it),end(it)));
	})
	<<'\n';
}


// wrong algorithm... now fixed. :(
// but taking about half an hour to find and fix the INT_MIN bug is still terribly slow.

// UPD: constant optimization.
// may need to swap dimension of value.
// The complexity is about 2e9. No wonder it gets TLE.

#ifndef LOCAL
#define NDEBUG 1
#endif
#include<bits/stdc++.h>

int const numChar=14;

struct AhoCorasick{
	using NodeIndex=int;
	struct Node{
		NodeIndex sfxLink;
		int64_t cost; // minimum len of a dict word reachable via sfxLink, or 0
		std::array<NodeIndex,numChar> child;
	};
	std::vector<Node> data;

	bool constructed=false;

	AhoCorasick(){
		newNode(); // root
	}

	NodeIndex newNode(){
		assert(!constructed);
		data.push_back({-1,0});
		for(auto & x: data.back().child) x=-1;
		assert(NodeIndex(data.size()-1)>=0);
		return NodeIndex(data.size()-1);
	}

	NodeIndex child(NodeIndex node,int letter){
		assert(0<=letter&&letter<numChar);
		assert(0<=node);
		if(data[node].child[letter]<0){
			assert(!constructed);
			NodeIndex z=newNode();
			assert(z>=0);
			data[node].child[letter]=z;
			assert(data[node].child[letter]>=0);
		}else{
			assert(data[node].child[letter]>=0);
		}
		assert(data[node].child[letter]>=0);
		return data[node].child[letter];
	}

	NodeIndex next(int node, int direction) const{
		return data[node].child[direction];
	}

	void build(){
		assert(!constructed);
		constructed=true;

		std::queue<NodeIndex> q;
		q.push(0);
		while(!q.empty()){
			NodeIndex node=q.front();q.pop();
			assert(node==0||data[node].sfxLink>=0);
			for(int letter=0;letter<numChar;++letter){
				NodeIndex& child_=data[node].child[letter];
				if(child_<0){
					child_=node==0 ? 0: data[data[node].sfxLink].child[letter];
					assert(child_>=0);
					continue;
				}

				// construct sfxLink for child_
				NodeIndex z=data[node].sfxLink;
				while(z>=0&&data[z].child[letter]<0)
					z=data[z].sfxLink;
				if(z<0)
					data[child_].sfxLink=0;
				else
					data[child_].sfxLink=data[z].child[letter];

				// construct cost for child_
				data[child_].cost+=data[data[child_].sfxLink].cost;
				q.push(child_);
			}
		}
	}

};

int main(){
	std::ios::sync_with_stdio(0);std::cin.tie(0);
	int numString; std::cin>>numString;

	AhoCorasick tree;
	std::string string;
	for(int _=0;_<numString;++_) {
		int cost;
		std::cin>>string>>cost;
		assert(std::abs(cost)<=1000000);

		int node=0;
		for(auto ch: string) node=tree.child(node, ch-'a');
		tree.data[node].cost+=cost;
	}
	tree.build();
	assert(tree.data[0].cost==0);

	int const numMask=1<<numChar;
	std::vector<std::array<int64_t, numMask>> value(tree.data.size());
	// [node][mask of used characters] = maximum
	for(auto & it: value) std::fill(begin(it),end(it),
			std::numeric_limits<std::decay_t<decltype(it[0])>>::min());
	value[0][0]=0;

	struct item{
		int node; int64_t addedValue;
	};
	std::vector<item> pending(tree.data.size());

	auto const resetPending=[&]{
		assert(pending.size()==tree.data.size());
		for(int node=0;node<(int)pending.size();++node) pending[node]={node, 0};
	};
	resetPending();

	auto const applyPending=[&]{
		std::vector<int64_t> newValue(tree.data.size());

		for(auto mask=numMask; mask--;){
			// note: is it more memory efficient to swap node and mask?

			std::fill(begin(newValue),end(newValue), INT64_MIN);
			for(int node=0;node<(int)tree.data.size();++node) {
				auto const cost=value[node][mask];
				if(cost==INT64_MIN) continue;

				auto const [newNode, addedCost]=pending[node];
				assert(newNode>=0);
				newValue[newNode]=std::max(newValue[newNode], cost+addedCost);
			}
			for(int node=0;node<(int)tree.data.size();++node)
				value[node][mask]=newValue[node];
		}

		resetPending();
	};

	auto const addPendingChar=[&](int direction){
		for(auto &[node, value]: pending){
			node=tree.next(node, direction);
			value+=tree.data[node].cost;
		}
	};

	std::cin>>string;
	for(auto ch: string){
		if(ch=='?'){
			applyPending();

			for(auto mask=numMask; mask--;){
				// now value at mask>mask is new, value at mask<=mask is old
				for(int node=0;node<(int)tree.data.size();++node) {
					auto const cost=value[node][mask]; if(cost==std::numeric_limits<decltype(cost)>::min()) continue;
					for(auto tmp=(numMask-1) xor mask; tmp; tmp-=tmp&-tmp) {
						auto const ch=__builtin_ctz(tmp);
						auto const newNode=tree.next(node, ch);
						auto& it=value[newNode][mask|1<<ch];
						it=std::max(it, cost+tree.data[newNode].cost);
					}
				}
				for(int node=0;node<(int)tree.data.size();++node) {
					value[node][mask]=std::numeric_limits<std::decay_t<decltype(value[node][mask])>>::min();
				}
				// now value at mask>=mask is new, value at mask<mask is old
			}
		}else{
			addPendingChar(ch-'a');
		}
	}
	applyPending();

	std::cout<<std::accumulate(begin(value),end(value), INT64_MIN,[&](int64_t value, auto const& it){
		return std::max(value, *std::max_element(begin(it),end(it)));
	})
	<<'\n';
}


// :/

#ifndef LOCAL
#define NDEBUG 1
#endif
#include<bits/stdc++.h>

struct AhoCorasick{
	using NodeIndex=int;
	struct Node{
		NodeIndex sfxLink;
		int64_t cost; // minimum len of a dict word reachable via sfxLink, or 0
		std::array<NodeIndex,14> child;
	};
	std::vector<Node> data;

	bool constructed=false;

	AhoCorasick(){
		newNode(); // root
	}

	NodeIndex newNode(){
		assert(!constructed);
		data.push_back({-1,0});
		for(auto & x: data.back().child) x=-1;
		assert(NodeIndex(data.size()-1)>=0);
		return NodeIndex(data.size()-1);
	}

	NodeIndex child(NodeIndex node,int letter){
		assert(0<=letter&&letter<14);
		assert(0<=node);
		if(data[node].child[letter]<0){
			assert(!constructed);
			NodeIndex z=newNode();
			assert(z>=0);
			data[node].child[letter]=z;
			assert(data[node].child[letter]>=0);
		}else{
			assert(data[node].child[letter]>=0);
		}
		assert(data[node].child[letter]>=0);
		return data[node].child[letter];
	}

	NodeIndex next(int node, int direction) const{
		return data[node].child[direction];
	}

	void build(){
		assert(!constructed);
		constructed=true;

		std::queue<NodeIndex> q;
		q.push(0);
		while(!q.empty()){
			NodeIndex node=q.front();q.pop();
			assert(node==0||data[node].sfxLink>=0);
			for(int letter=0;letter<14;++letter){
				NodeIndex& child_=data[node].child[letter];
				if(child_<0){
					child_=node==0 ? 0: data[data[node].sfxLink].child[letter];
					assert(child_>=0);
					continue;
				}

				// construct sfxLink for child_
				NodeIndex z=data[node].sfxLink;
				while(z>=0&&data[z].child[letter]<0)
					z=data[z].sfxLink;
				if(z<0)
					data[child_].sfxLink=0;
				else
					data[child_].sfxLink=data[z].child[letter];

				// construct cost for child_
				data[child_].cost+=data[data[child_].sfxLink].cost;
				q.push(child_);
			}
		}
	}

};

int main(){
	std::ios::sync_with_stdio(0);std::cin.tie(0);
	int numString; std::cin>>numString;

	AhoCorasick tree;
	std::string string;
	for(int _=0;_<numString;++_) {
		int cost;
		std::cin>>string>>cost;
		assert(std::abs(cost)<=1000000);

		int node=0;
		for(auto ch: string) node=tree.child(node, ch-'a');
		tree.data[node].cost+=cost;
	}
	tree.build();
	assert(tree.data[0].cost==0);

	struct item{
		int node; int64_t maximumValue;
		bool operator<(item other) const{ return maximumValue<other.maximumValue; }
#if not NDEBUG
		bool operator==(item other) const{ return std::tie(node, maximumValue)==
			std::tie(other.node, other.maximumValue); }
#endif
	};
	std::vector<item> value(1<<14, {-1, INT64_MIN}); // [mask of used characters]
	value[0]={0, 0};

	std::vector<item> pending(tree.data.size());
	auto const resetPending=[&]{
		assert(pending.size()==tree.data.size());
		for(int node=0;node<(int)pending.size();++node) pending[node]={node, 0};
	};
	resetPending();

	std::cin>>string;
	std::vector<item> newValue; // temporary, to avoid memory reallocation

	auto const resetNewValue=[&]{
		newValue.assign(1<<14, {-1, INT64_MIN});
	};

	auto const applyPending=[&]{
		resetNewValue();

		for(auto mask=value.size(); mask--;){
			auto const [node, cost]=value[mask];
			if(node<0) { assert(cost==INT64_MIN); continue; }

			auto& it=newValue[mask];
			auto const [newNode, addedCost]=pending[node];
			assert(newNode>=0);
			it=std::max(it, {newNode, cost+addedCost});
		}

		std::swap(value, newValue);

		resetPending();
	};

	for(auto ch: string){
		if(ch=='?'){
			applyPending();

			resetNewValue();
			for(auto mask=value.size(); mask--;){
				auto const [node, cost]=value[mask];
				if(node<0) continue;
				for(int ch=0;ch<14;++ch) if(not(mask>>ch&1)){
					auto& it=newValue[mask|1<<ch];
					auto const newNode=tree.next(node, ch);
					it=std::max(it, {newNode, cost+tree.data[newNode].cost});
				}
			}
			std::swap(value, newValue);
		}else{

			ch=char(ch-'a');

			for(auto &[node, value]: pending){
				node=tree.next(node, ch);
				value+=tree.data[node].cost;
			}

			/*
			resetNewValue();
			for(auto mask=value.size(); mask--;){
				auto const [node, cost]=value[mask];
				if(node<0) continue;
				auto const newNode=tree.next(node, ch);
				newValue[mask]=std::max(newValue[mask], {newNode, cost+tree.data[newNode].cost});
			}
			*/

		}

	}
	applyPending();

	std::cout<<std::max_element(begin(value),end(value))->maximumValue<<'\n';
}


// ??????
// [26] instead of [14] should only consume more memory, not wrong answer...?
//
// (calling applyPending in debug may change the program behavior if it's incorrect.)
//
// {}

#ifndef LOCAL
#define NDEBUG 1
#endif
#include<bits/stdc++.h>

struct AhoCorasick{
	using NodeIndex=int;
	struct Node{
		NodeIndex sfxLink;
		int64_t cost; // minimum len of a dict word reachable via sfxLink, or 0
		std::array<NodeIndex,14> child;
	};
	std::vector<Node> data;

	bool constructed=false;

	AhoCorasick(){
		newNode(); // root
	}

	NodeIndex newNode(){
		assert(!constructed);
		data.push_back({-1,0});
		for(auto & x: data.back().child) x=-1;
		assert(NodeIndex(data.size()-1)>=0);
		return NodeIndex(data.size()-1);
	}

	NodeIndex child(NodeIndex node,int letter){
		assert(0<=letter&&letter<14);
		assert(0<=node);
		if(data[node].child[letter]<0){
			assert(!constructed);
			NodeIndex z=newNode();
			assert(z>=0);
			data[node].child[letter]=z;
			assert(data[node].child[letter]>=0);
		}else{
			assert(data[node].child[letter]>=0);
		}
		assert(data[node].child[letter]>=0);
		return data[node].child[letter];
	}

	NodeIndex next(int node, int direction) const{
		return data[node].child[direction];
	}

	void build(){
		assert(!constructed);
		constructed=true;

		std::queue<NodeIndex> q;
		q.push(0);
		while(!q.empty()){
			NodeIndex node=q.front();q.pop();
			assert(node==0||data[node].sfxLink>=0);
			for(int letter=0;letter<14;++letter){
				NodeIndex& child_=data[node].child[letter];
				if(child_<0){
					child_=node==0 ? 0: data[data[node].sfxLink].child[letter];
					assert(child_>=0);
					continue;
				}

				// construct sfxLink for child_
				NodeIndex z=data[node].sfxLink;
				while(z>=0&&data[z].child[letter]<0)
					z=data[z].sfxLink;
				if(z<0)
					data[child_].sfxLink=0;
				else
					data[child_].sfxLink=data[z].child[letter];

				// construct cost for child_
				data[child_].cost+=data[data[child_].sfxLink].cost;
				q.push(child_);
			}
		}
	}

};

int main(){
	std::ios::sync_with_stdio(0);std::cin.tie(0);
	int numString; std::cin>>numString;

	AhoCorasick tree;
	std::string string;
	for(int _=0;_<numString;++_) {
		int cost;
		std::cin>>string>>cost;
		assert(std::abs(cost)<=1000000);

		int node=0;
		for(auto ch: string) node=tree.child(node, ch-'a');
		tree.data[node].cost+=cost;
	}
	tree.build();
	assert(tree.data[0].cost==0);

	struct item{
		int node; int64_t maximumValue;
		bool operator<(item other) const{ return maximumValue<other.maximumValue; }
#if not NDEBUG
		bool operator==(item other) const{ return std::tie(node, maximumValue)==
			std::tie(other.node, other.maximumValue); }
#endif
	};
	std::vector<item> value(1<<14, {-1, INT_MIN}); // [mask of used characters]
	value[0]={0, 0};

	std::vector<item> pending(tree.data.size());
	auto const resetPending=[&]{
		assert(pending.size()==tree.data.size());
		for(int node=0;node<(int)pending.size();++node) pending[node]={node, 0};
	};
	resetPending();

	std::cin>>string;
	std::vector<item> newValue; // temporary, to avoid memory reallocation

	auto const resetNewValue=[&]{
		newValue.assign(1<<14, {-1, INT_MIN});
	};

	auto const applyPending=[&]{
		resetNewValue();

		for(auto mask=value.size(); mask--;){
			auto const [node, cost]=value[mask];
			if(node<0) { assert(cost==INT_MIN); continue; }

			auto& it=newValue[mask];
			auto const [newNode, addedCost]=pending[node];
			assert(newNode>=0);
			it=std::max(it, {newNode, cost+addedCost});
		}

		std::swap(value, newValue);

		resetPending();
	};

	for(auto ch: string){
		if(ch=='?'){
			applyPending();

			resetNewValue();
			for(auto mask=value.size(); mask--;){
				auto const [node, cost]=value[mask];
				if(node<0) continue;
				for(int ch=0;ch<14;++ch) if(not(mask>>ch&1)){
					auto& it=newValue[mask|1<<ch];
					auto const newNode=tree.next(node, ch);
					it=std::max(it, {newNode, cost+tree.data[newNode].cost});
				}
			}
			std::swap(value, newValue);
		}else{

			ch=char(ch-'a');

			for(auto &[node, value]: pending){
				node=tree.next(node, ch);
				value+=tree.data[node].cost;
			}

			/*
			resetNewValue();
			for(auto mask=value.size(); mask--;){
				auto const [node, cost]=value[mask];
				if(node<0) continue;
				auto const newNode=tree.next(node, ch);
				newValue[mask]=std::max(newValue[mask], {newNode, cost+tree.data[newNode].cost});
			}
			*/

		}

	}
	applyPending();

	std::cout<<std::max_element(begin(value),end(value))->maximumValue<<'\n';
}


// >.< I don't have a prepped template for AhoCorasick and looking for a working implementation
// is usually harder than reimplement from scratch...
//
// upd: took too long to debug the extraneous/missing std::swap(value, oldValue).
// I should've done static debug before dynamic debug...
//
// sorry for the warnings.

#ifndef LOCAL
#define NDEBUG 1
#endif
#include<bits/stdc++.h>

struct AhoCorasick{
	using NodeIndex=int;
	struct Node{
		NodeIndex sfxLink;
		int cost; // minimum len of a dict word reachable via sfxLink, or 0
		std::array<NodeIndex,26> child;
	};
	std::vector<Node> data;

	bool constructed=false;

	AhoCorasick(){
		newNode(); // root
	}

	NodeIndex newNode(){
		assert(!constructed);
		data.push_back({-1,0});
		for(auto & x: data.back().child) x=-1;
		assert(NodeIndex(data.size()-1)>=0);
		return data.size()-1;
	}

	NodeIndex child(NodeIndex node,int letter){
		assert(0<=letter&&letter<26);
		assert(0<=node);
		if(data[node].child[letter]<0){
			assert(!constructed);
			NodeIndex z=newNode();
			assert(z>=0);
			data[node].child[letter]=z;
			assert(data[node].child[letter]>=0);
		}else{
			assert(data[node].child[letter]>=0);
		}
		assert(data[node].child[letter]>=0);
		return data[node].child[letter];
	}

	NodeIndex next(int node, int direction) const{
		return data[node].child[direction];
	}

	void build(){
		assert(!constructed);
		constructed=true;

		std::queue<NodeIndex> q;
		q.push(0);
		while(!q.empty()){
			NodeIndex node=q.front();q.pop();
			assert(node==0||data[node].sfxLink>=0);
			for(int letter=0;letter<26;++letter){
				NodeIndex& child_=data[node].child[letter];
				if(child_<0){
					child_=node==0 ? 0: data[data[node].sfxLink].child[letter];
					assert(child_>=0);
					continue;
				}

				// construct sfxLink for child_
				NodeIndex z=data[node].sfxLink;
				while(z>=0&&data[z].child[letter]<0)
					z=data[z].sfxLink;
				if(z<0)
					data[child_].sfxLink=0;
				else
					data[child_].sfxLink=data[z].child[letter];

				// construct cost for child_
				data[child_].cost+=data[data[child_].sfxLink].cost;
				q.push(child_);
			}
		}
	}

};

int main(){
	std::ios::sync_with_stdio(0);std::cin.tie(0);
	int numString; std::cin>>numString;

	AhoCorasick tree;
	std::string string;
	for(int _=0;_<numString;++_) {
		int cost;
		std::cin>>string>>cost;
		assert(std::abs(cost)<=1000000);

		int node=0;
		for(auto ch: string) node=tree.child(node, ch-'a');
		tree.data[node].cost+=cost;
	}
	tree.build();
	assert(tree.data[0].cost==0);

	struct item{
		int node, maximumValue;
		bool operator<(item other) const{ return maximumValue<other.maximumValue; }
#if not NDEBUG
		bool operator==(item other) const{ return std::tie(node, maximumValue)==
			std::tie(other.node, other.maximumValue); }
#endif
	};
	std::vector<item> value(1<<14, {-1, INT_MIN}); // [mask of used characters]
	value[0]={0, 0};

	std::vector<item> pending(tree.data.size());
	auto const resetPending=[&]{
		assert(pending.size()==tree.data.size());
		for(int node=0;node<(int)pending.size();++node) pending[node]={node, 0};
	};
	resetPending();

	std::cin>>string;
	std::vector<item> newValue; // temporary, to avoid memory reallocation

	auto const resetNewValue=[&]{
		newValue.assign(1<<14, {-1, INT_MIN});
	};

#if not NDEBUG
	auto const nonePending=[&]{
		auto old=pending;
		resetPending();
		std::swap(pending, old);
		return old==pending;
	};
#endif

	auto const applyPending=[&]{
		bool nonePending_;
		assert((nonePending_=nonePending(), true));
		resetNewValue();

		for(auto mask=value.size(); mask--;){
			auto const [node, cost]=value[mask];
			if(node<0) { assert(cost==INT_MIN); continue; }

			auto& it=newValue[mask];
			auto const [newNode, addedCost]=pending[node];
			assert(newNode>=0);
			it=std::max(it, {newNode, cost+addedCost});
		}

		assert([&]{
				auto const x=std::max_element(begin(value),end(value))->maximumValue;
				auto const y=std::max_element(begin(newValue),end(newValue))->maximumValue;
				//assert(x<=y);
				return true;
		}());
		std::swap(value, newValue);

		assert(nonePending_ ? value==newValue: true);

		resetPending();
	};

	for(auto ch: string){
		if(ch=='?'){
			applyPending();

			resetNewValue();
			for(auto mask=value.size(); mask--;){
				auto const [node, cost]=value[mask];
				if(node<0) continue;
				for(int ch=0;ch<14;++ch) if(not(mask>>ch&1)){
					auto& it=newValue[mask|1<<ch];
					auto const newNode=tree.next(node, ch);
					it=std::max(it, {newNode, cost+tree.data[newNode].cost});
				}
			}
			std::swap(value, newValue);
		}else{
			assert((applyPending(), true));

			ch-='a';

			for(auto &[node, value]: pending){
				node=tree.next(node, ch);
				value+=tree.data[node].cost;
			}

			/*
			resetNewValue();
			for(auto mask=value.size(); mask--;){
				auto const [node, cost]=value[mask];
				if(node<0) continue;
				auto const newNode=tree.next(node, ch);
				newValue[mask]=std::max(newValue[mask], {newNode, cost+tree.data[newNode].cost});
			}
			*/

			assert((applyPending(), true));
		}

	}
	applyPending();

	std::cout<<std::max_element(begin(value),end(value))->maximumValue<<'\n';
}


#include <bits/stdc++.h>

using namespace std;

using ll = long long;

mt19937 mt(736);

class trie
{
	struct node
	{
		using nodeptr = node *;

		array<nodeptr, 14> nxt;
		nodeptr link;
		int x;

		node() : x(0), link(nullptr)
		{
			fill(nxt.begin(), nxt.end(), nullptr);
		}

		~node()
		{
			for (auto it : nxt)
				delete it;
		}
	};

	node root;

public:
	trie()
	{
		root.link = &root;
	}

	void add(const string &str, int w)
	{
		auto last = &root;

		for (auto it : str)
		{
			it -= 'a';

			if (last->nxt[it] == nullptr)
				last->nxt[it] = new node();

			last = last->nxt[it];
		}

		last->x += w;
	}

	void build()
	{
		queue<tuple<node *, node *, int>> q;

		for (size_t i = 0; i < root.nxt.size(); i++)
			q.emplace(root.nxt[i], &root, i);

		while (!q.empty())
		{
			node *h, *p;
			int i;

			tie(h, p, i) = q.front();
			q.pop();

			if (h == nullptr)
				continue;

			auto prev = p->link;

			while ((prev->nxt[i] == nullptr || prev->nxt[i] == h) && prev->link != prev)
				prev = prev->link;

			h->link = prev->nxt[i] == nullptr || prev->nxt[i] == h ? prev : prev->nxt[i];
			h->x += h->link->x;

			for (int j = 0; j < h->nxt.size(); j++)
				q.emplace(h->nxt[j], h, j);
		}
	}

	ll solve(const string &str)
	{
		map<pair<unsigned, node *>, ll> dp;
		dp[{0, &root}] = 0;

		auto move = [](auto &q, auto sec, int i)
		{
			while (q->link != q && q->nxt[i] == nullptr)
				q = q->link;

			if (q->nxt[i] != nullptr)
				q = q->nxt[i];

			sec += q->x;

			return sec;
		};

		for (auto it : str)
		{
			map<pair<unsigned, node *>, ll> tmp;

			if (it == '?')
			{
				for (auto wh : dp)
					for (size_t i = 0; i < root.nxt.size(); i++)
						if (((1u << i) & wh.first.first) == 0)
						{
							auto msk = (1u << i) | wh.first.first;
							auto q = wh.first.second;
							auto sec = move(q, wh.second, i);

							if (tmp.count({msk, q}) == 0)
								tmp.emplace(pair{msk, q}, numeric_limits<ll>::min());

							tmp[{msk, q}] = max(tmp[{msk, q}], sec);
						}

			}
			else
			{
				for (const auto &wh : dp)
				{
					auto msk = wh.first.first;
					auto q = wh.first.second;
					auto sec = move(q, wh.second, it - 'a');

					if (tmp.count({msk, q}) == 0)
						tmp.emplace(pair{msk, q}, numeric_limits<ll>::min());

					tmp[{msk, q}] = max(tmp[{msk, q}], sec);
				}
			}

			dp.swap(tmp);
		}

		ll ans = numeric_limits<ll>::min();

		for (auto it : dp)
			ans = max(ans, it.second);

		return ans;
	}
};

void solve(istream &cin = std::cin, ostream &cout = std::cout)
{
	trie tr;

	int k;

	cin >> k;

	for (int i = 0; i < k; i++)
	{
		string str;
		int w;

		cin >> str >> w;

		tr.add(str, w);
	}

	tr.build();

	string str;

	cin >> str;

	cout << tr.solve(str) << endl;
}

int main()
{
	ios_base::sync_with_stdio(false);
	cin.tie(nullptr);

	cout << fixed;

#ifdef LOCAL
	auto st = clock();

	ifstream fin("../input.txt");

	solve(fin);

	cout << "clock: " << setprecision(4) << (clock() - st) / (double) CLOCKS_PER_SEC << endl;
#else
	solve();
#endif

	return 0;
}


#include <bits/stdc++.h>

using namespace std;

using ll = long long;

mt19937 mt(736);

class trie
{
	struct node
	{
		using nodeptr = node *;

		array<nodeptr, 14> nxt;
		nodeptr link;
		int x;

		node() : x(0), link(nullptr)
		{
			fill(nxt.begin(), nxt.end(), nullptr);
		}

		~node()
		{
			for (auto it : nxt)
				delete it;
		}
	};

	node root;

public:
	trie()
	{
		root.link = &root;
	}

	void add(const string &str, int w)
	{
		auto last = &root;

		for (auto it : str)
		{
			it -= 'a';

			if (last->nxt[it] == nullptr)
				last->nxt[it] = new node();

			last = last->nxt[it];
		}

		last->x += w;
	}

	void build()
	{
		queue<tuple<node *, node *, int>> q;

		for (size_t i = 0; i < root.nxt.size(); i++)
			q.emplace(root.nxt[i], &root, i);

		while (!q.empty())
		{
			node *h, *p;
			int i;

			tie(h, p, i) = q.front();
			q.pop();

			if (h == nullptr)
				continue;

			auto prev = p->link;

			while ((prev->nxt[i] == nullptr || prev->nxt[i] == h) && prev->link != prev)
				prev = prev->link;

			h->link = prev->nxt[i] == nullptr || prev->nxt[i] == h ? prev : prev->nxt[i];
			h->x += h->link->x;

			for (int j = 0; j < h->nxt.size(); j++)
				q.emplace(h->nxt[j], h, j);
		}
	}

	ll solve(const string &str)
	{
		vector<unsigned> cur = {0};
		array<unordered_map<node *, ll>, 1u << 14u> dp, tmp;
		dp[0][&root] = 0;

		auto move = [](auto &q, auto sec, int i)
		{
			while (q->link != q && q->nxt[i] == nullptr)
				q = q->link;

			if (q->nxt[i] != nullptr)
				q = q->nxt[i];

			sec += q->x;

			return sec;
		};

		for (auto it : str)
		{
			for (auto t : cur)
				tmp[t].clear();

			if (it == '?')
			{
				for (auto wff : cur)
					for (auto wh : dp[wff])
						for (size_t i = 0; i < root.nxt.size(); i++)
							if (((1u << i) & wff) == 0)
							{
								auto msk = (1u << i) | wff;
								auto q = wh.first;
								auto sec = move(q, wh.second, i);

								if (tmp[msk].count(q) == 0)
									tmp[msk].emplace(q, numeric_limits<ll>::min());

								tmp[msk][q] = max(tmp[msk].at(q), sec);
							}

				auto pc = __builtin_popcount(cur.front()) + 1;
				cur.clear();

				for (unsigned i = 0; i < (1u << 14u); i++)
					if (__builtin_popcount(i) == pc)
						cur.push_back(i);
			}
			else
			{
				for (auto wff : cur)
					for (const auto &wh : dp[wff])
					{
						auto msk = wff;
						auto q = wh.first;
						auto sec = move(q, wh.second, it - 'a');

						if (tmp[msk].count(q) == 0)
							tmp[msk].emplace(q, numeric_limits<ll>::min());

						tmp[msk][q] = max(tmp[msk].at(q), sec);
					}
			}

			dp.swap(tmp);
		}

		ll ans = numeric_limits<ll>::min();

		for (auto wff : cur)
			for (auto it : dp[wff])
				ans = max(ans, it.second);

		return ans;
	}
};

void solve(istream &cin = std::cin, ostream &cout = std::cout)
{
	trie tr;

	int k;

	cin >> k;

	for (int i = 0; i < k; i++)
	{
		string str;
		int w;

		cin >> str >> w;

		tr.add(str, w);
	}

	tr.build();

	string str;

	cin >> str;

	cout << tr.solve(str) << endl;
}

int main()
{
	ios_base::sync_with_stdio(false);
	cin.tie(nullptr);

	cout << fixed;

#ifdef LOCAL
	auto st = clock();

	ifstream fin("../input.txt");

	solve(fin);

	cout << "clock: " << setprecision(4) << (clock() - st) / (double) CLOCKS_PER_SEC << endl;
#else
	solve();
#endif

	return 0;
}


#include <bits/stdc++.h>

using namespace std;

using ll = long long;

mt19937 mt(736);

const size_t alph = 14;

template<class T>
auto &remax(T &x, const T &y)
{
	return x = max(x, y);
}

template<class T>
auto &remin(T &x, const T &y)
{
	return x = min(x, y);
}

class crucian
{
	struct node
	{
		array<int, alph> nxt;
		int link, x;

	public:
		node() : link(-1), x(0)
		{
			fill(nxt.begin(), nxt.end(), -1);
		}

		int &operator[](int v)
		{
			return nxt[v];
		}
	};

	vector<node> mem;

	int trie_move(int v, int q)
	{
		if (mem[v][q] == -1)
		{
			mem[v][q] = mem.size();
			mem.emplace_back();
		}

		return mem[v][q];
	}

public:
	crucian() : mem(1)
	{}

	size_t size() const
	{
		return mem.size();
	}

	void insert(const string &str, int w)
	{
		auto cur = 0;

		for (auto it : str)
			cur = trie_move(cur, it - 'a');

		mem[cur].x += w;
	}

	void build()
	{
		queue<tuple<int, int, int>> q;
		q.emplace(0, -1, -1);

		while (!q.empty())
		{
			int v, p, i;

			tie(v, p, i) = q.front();
			q.pop();

			if (v != -1)
			{
				if (p != -1)
				{
					auto prev = p;

					while ((mem[prev][i] == -1 || mem[prev][i] == v) && prev != 0)
						prev = mem[prev].link;

					if (mem[prev][i] == -1 || mem[prev][i] == v)
						mem[v].link = prev;
					else
						mem[v].link = mem[prev][i];

					mem[v].x += mem[mem[v].link].x;
				}
				else
					mem[v].link = 0;

				for (int j = 0; j < mem[v].nxt.size(); j++)
					q.emplace(mem[v][j], v, j);
			}
		}
	}

	ll walk(const string &str, int &v)
	{
		ll ans = 0;

		for (auto it : str)
		{
			assert(it != '?');

			it -= 'a';

			while (v != 0 && mem[v][it] == -1)
				v = mem[v].link;

			if (mem[v][it] != -1)
				v = mem[v][it];

			ans += mem[v].x;
		}

		return ans;
	}

	pair<ll, int> mem_walk(const vector<string> &pal, int q, int st)
	{
		static vector<vector<pair<ll, int>>> dp(pal.size(), vector<pair<ll, int>>(mem.size(), {0, -1}));

		if (dp[q][st].second == -1)
		{
			auto cur = st;

			dp[q][st].first = walk(pal[q], cur);
			dp[q][st].second = cur;
		}

		return dp[q][st];
	}
};

void solve(istream &cin = std::cin, ostream &cout = std::cout)
{
	int k;

	cin >> k;

	crucian tr;

	for (int i = 0; i < k; i++)
	{
		string str;
		int w;

		cin >> str >> w;

		tr.insert(str, w);
	}

	tr.build();

	string str;

	cin >> str;

	int st = 0;
	auto nxt = str.find('?');
	ll ans = tr.walk(str.substr(0, nxt), st);

	str = nxt == std::string::npos ? "" : str.substr(nxt);

	vector<string> parts;

	while (!str.empty())
	{
		assert(str.front() == '?');

		nxt = str.find('?', 1);

		parts.push_back(str.substr(1, nxt - 1));
		str = nxt == std::string::npos ? "" : str.substr(nxt);
	}

	vector<vector<ll>> dp(1u << alph, vector<ll>(tr.size(), numeric_limits<ll>::min()));
	dp[0][st] = ans;

	for (unsigned mask = 0; mask < dp.size(); mask++)
		for (int q = 0; q < dp[mask].size(); q++)
			if (dp[mask][q] != numeric_limits<ll>::min())
			{
				auto pc = __builtin_popcount(mask);

				if (pc < parts.size())
					for (unsigned i = 0; i < alph; i++)
						if ((mask & (1u << i)) == 0)
						{
							int v = q;

							ll add = tr.walk(string(1, 'a' + i), v);
							auto tmp = tr.mem_walk(parts, pc, v);
							add += tmp.first;
							v = tmp.second;

							remax(dp[mask | (1u << i)][v], dp[mask][q] + add);
						}
			}

	ans = numeric_limits<ll>::min();

	for (unsigned mask = 0; mask < dp.size(); mask++)
		if (__builtin_popcount(mask) == parts.size())
			remax(ans, *max_element(dp[mask].begin(), dp[mask].end()));

	cout << ans << endl;
}

int main()
{
	ios_base::sync_with_stdio(false);
	cin.tie(nullptr);

	cout << fixed;

#ifdef LOCAL
	auto st = clock();

	ifstream fin("../input.txt");

	solve(fin);

	cout << "clock: " << setprecision(4) << (clock() - st) / (double) CLOCKS_PER_SEC << endl;
#else
	solve();
#endif

	return 0;
}


#include <bits/stdc++.h>

using namespace std;

using ll = long long;

mt19937 mt(736);

class trie
{
	struct node
	{
		using nodeptr = node *;

		array<nodeptr, 14> nxt;
		nodeptr link;
		int x;

		node() : x(0), link(nullptr)
		{
			fill(nxt.begin(), nxt.end(), nullptr);
		}

		~node()
		{
			for (auto it : nxt)
				delete it;
		}
	};

	node root;

public:
	trie()
	{
		root.link = &root;
	}

	void add(const string &str, int w)
	{
		auto last = &root;

		for (auto it : str)
		{
			it -= 'a';

			if (last->nxt[it] == nullptr)
				last->nxt[it] = new node();

			last = last->nxt[it];
		}

		last->x += w;
	}

	void build()
	{
		queue<tuple<node *, node *, int>> q;

		for (size_t i = 0; i < root.nxt.size(); i++)
			q.emplace(root.nxt[i], &root, i);

		while (!q.empty())
		{
			node *h, *p;
			int i;

			tie(h, p, i) = q.front();
			q.pop();

			if (h == nullptr)
				continue;

			auto prev = p->link;

			while ((prev->nxt[i] == nullptr || prev->nxt[i] == h) && prev->link != prev)
				prev = prev->link;

			h->link = prev->nxt[i] == nullptr || prev->nxt[i] == h ? prev : prev->nxt[i];
			h->x += h->link->x;

			for (int j = 0; j < h->nxt.size(); j++)
				q.emplace(h->nxt[j], h, j);
		}
	}

	ll solve(const string &str)
	{
		map<pair<unsigned, node *>, ll> dp;
		dp[{0, &root}] = 0;

		auto move = [](auto &q, auto sec, int i)
		{
			while (q->link != q && q->nxt[i] == nullptr)
				q = q->link;

			if (q->nxt[i] != nullptr)
				q = q->nxt[i];

			sec += q->x;

			return sec;
		};

		for (auto it : str)
		{
			typeof(dp) tmp;

			if (it == '?')
			{
				for (auto wh : dp)
					for (size_t i = 0; i < root.nxt.size(); i++)
						if (((1u << i) & wh.first.first) == 0)
						{
							auto msk = (1u << i) | wh.first.first;
							auto q = wh.first.second;
							auto sec = move(q, wh.second, i);

							if (tmp.count({msk, q}) == 0)
								tmp.emplace(pair{msk, q}, numeric_limits<ll>::min());

							tmp[{msk, q}] = max(tmp[{msk, q}], sec);
						}

			}
			else
			{
				for (const auto &wh : dp)
				{
					auto msk = wh.first.first;
					auto q = wh.first.second;
					auto sec = move(q, wh.second, it - 'a');

					if (tmp.count({msk, q}) == 0)
						tmp.emplace(pair{msk, q}, numeric_limits<ll>::min());

					tmp[{msk, q}] = max(tmp[{msk, q}], sec);
				}
			}

			dp.swap(tmp);
		}

		ll ans = numeric_limits<ll>::min();

		for (auto it : dp)
			ans = max(ans, it.second);

		return ans;
	}
};

void solve(istream &cin = std::cin, ostream &cout = std::cout)
{
	trie tr;

	int k;

	cin >> k;

	for (int i = 0; i < k; i++)
	{
		string str;
		int w;

		cin >> str >> w;

		tr.add(str, w);
	}

	tr.build();

	string str;

	cin >> str;

	cout << tr.solve(str) << endl;
}

int main()
{
	ios_base::sync_with_stdio(false);
	cin.tie(nullptr);

	cout << fixed;

#ifdef LOCAL
	auto st = clock();

	ifstream fin("../input.txt");

	solve(fin);

	cout << "clock: " << setprecision(4) << (clock() - st) / (double) CLOCKS_PER_SEC << endl;
#else
	solve();
#endif

	return 0;
}


#define _CRT_SECURE_NO_WARNINGS
#define REL

#pragma GCC target ("avx2")
#pragma GCC optimize("Ofast")
#pragma GCC optimize("unroll-loops")

#include <iostream>
#include <vector>
#include <algorithm>
#include <cmath>
#include <deque>
#include <queue>
#include <iomanip>
#include <cstdio>
#include <unordered_map>
#include <unordered_set>
#include <map>
#include <string>
#include <set>
#include <bitset>
#include <cstdint>
#include <cstring>
#include <cassert>
#include <complex>
#include <stack>
#include <random>

using namespace std;

typedef int64_t i64;
typedef long long LL;
typedef long long ll;
typedef long double LD;
typedef complex <LD> cld;
typedef unsigned int uint;
typedef complex <double> cd;
typedef unsigned long long ULL;

#define endl "\n"
#define dendl "\n\n"
#define mp make_pair
#define mt make_tuple
#define size(v) (int)v.size()
#define all(v) v.begin(), v.end()
#define rall(v) v.rbegin(), v.rend()

/////////////////////////////////////////////////////////////////////

template <typename T>
T sqr(T a) {
	return a * a;
}

template <typename T>
int sign(T a) {
	if (a == 0)
		return 0;
	return (a > 0 ? 1 : -1);
}

template <typename T>
bool uax(T& a, const T b) {
	if (a < b) {
		a = b;
		return true;
	}
	return false;
}

template <typename T>
bool uin(T& a, const T b) {
	if (a > b) {
		a = b;
		return true;
	}
	return false;
}

template <typename T1, typename T2>
istream& operator >> (istream& in, pair<T1, T2>& p) {
	in >> p.first >> p.second;
	return in;
}

template <typename T1, typename T2>
ostream& operator << (ostream& out, pair<T1, T2>& p) {
	out << p.first << ' ' << p.second;
	return out;
}

mt19937_64 rnd(0);

const int LOG = 30;
const int N = (int)2e5 + 7;
const int MAXN = (int)1e3 + 7;
const int MOD = (int)998244353;
const int INF = (int)1e9 + 7;
const int CONST = 450;
const LL LINF = (i64)1e17;
const LD PI = 3.1415926535897932384626433832795;
const LD EPS = 1;

/////////////////////////////////////////////////////////////////////

struct Node {
	Node* suff, * edg[14], * shift[15];
	LL sum, cost, sum_shift[15];
	int node;
	Node() {
		sum = cost = 0;
		suff = nullptr;
		fill(edg, edg + 14, nullptr);
		fill(shift, shift + 14, nullptr);
	}
};

int node;
Node arr[MAXN];

Node* new_node() {
	arr[node].node = node;
	return &arr[node++];
}

Node* fict, * root;

void add(Node *v, const string& s, int cost) {
	for (char c : s) {
		int pos = c - 'a';
		if (!v->edg[pos])
			v->edg[pos] = new_node();
		v = v->edg[pos];
	}
	v->cost += cost;
}

Node* go(Node* v, int pos) {
	if (v->edg[pos])
		return v->edg[pos];
	return go(v->suff, pos);
}

pair <Node*, LL> trav(Node* v, const string& s) {
	LL sum = v->sum;
	for (char c : s) {
		int pos = c - 'a';
		v = v->edg[pos];
		sum += v->sum;
	}
	return mp(v, sum);
}

void calc_suff() {
	root->suff = fict;
	for (int i = 0; i < 14; i++)
		fict->edg[i] = root;
	queue <Node*> q;
	q.push(root);
	while (!q.empty()) {
		auto v = q.front();
		q.pop();
		for (int i = 0; i < 14; i++) {
			if (!v->edg[i]) {
				v->edg[i] = go(v, i);
				continue;
			}
			Node* to = v->edg[i], * suff = go(v->suff, i);
			to->suff = suff;
			to->sum = to->cost + suff->sum;
			q.push(v->edg[i]);
		}
	}
}

string s;
int n, bit[N];
vector <string> seq;
LL dp[1 << 14][MAXN];

void solve() {
	for (int i = 1; i < N; i++)
		bit[i] = bit[i / 2] + (i & 1);
	fict = new_node(), root = new_node();
	cin >> n;
	for (int i = 0; i < n; i++) {
		string s; cin >> s;
		int cost; cin >> cost;
		add(root, s, cost);
	}
	cin >> s;
	calc_suff();
	int ind = 0, sz = size(s);
	while (ind < sz) {
		string cur = "";
		while (ind < sz && s[ind] != '?')
			cur += s[ind++];
		seq.push_back(cur);
		ind++;
	}
	if (s.back() == '?')
		seq.push_back("");
	int seg = size(seq);
	for (int i = 0; i < node; i++) {
		for (int j = 0; j < seg; j++)
			tie(arr[i].shift[j], arr[i].sum_shift[j]) = trav(&arr[i], seq[j]);
	}
	for (int i = 0; i < (1 << 14); i++)
		fill(dp[i], dp[i] + node, -LINF);
	auto st = trav(root, seq[0]);
	dp[0][st.first->node] = st.second;
	LL ans = -LINF;
	for (int mask = 0; mask < (1 << 14); mask++) {
		if (bit[mask] == seg - 1) {
			for (int cnode = 1; cnode < node; cnode++)
				uax(ans, dp[mask][cnode]);
		}
		if (bit[mask] >= seg - 1)
			continue;
		for (int cnode = 1; cnode < node; cnode++) {
			if (dp[mask][cnode] == -LINF)
				continue;
			for (int let = 0; let < 14; let++) {
				if (mask & (1 << let))
					continue;
				int nmask = mask | (1 << let);
				int nnode = arr[cnode].edg[let]->node;
				LL add = arr[nnode].sum_shift[bit[nmask]];
				nnode = arr[nnode].shift[bit[nmask]]->node;
				uax(dp[nmask][nnode], dp[mask][cnode] + add);
			}
		}
	}
	cout << ans;
}

int main() {
	double start = clock();
#ifdef _DEBUG
	freopen("input.txt", "r", stdin);
	freopen("output.txt", "w", stdout);
	cout << fixed << setprecision(20);
	cerr << fixed << setprecision(20);
#else
	cout << fixed << setprecision(20);
	ios_base::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	rnd.seed(time(0));
#endif

	int t = 1;
	for (int i = 0; i < t; i++) {
		solve();
	}

#ifdef _DEBUG
	cout << endl << "Time is " << (clock() - start) / 1000.0;
#endif
}


#define MST(a, b) memset(a,b,sizeof(a))
#define ALL(v) (v).begin(),(v).end()
#include <bits/stdc++.h>

using namespace std;
typedef long long LL;
typedef pair<int, int> pii;
typedef pair<LL,LL> pll;
#include <ext/pb_ds/priority_queue.hpp>
#define rep(i,a,b) for(int i = a ; i <= b ; ++i)
#define per(i,a,b) for(int i = a ; i >= b ; --i)
#define fors(i,S) for(int i = S ; i ; i = (i-1)&S)
#define forn(i,n) for(int i = 0 ; i < n ; ++i)

using namespace __gnu_pbds;
//typedef __gnu_pbds::priority_queue<pii, greater<pii>, pairing_heap_tag> heap;
//typedef heap::point_iterator iter;
typedef unsigned long long ULL;
//typedef __int128 LL128;
mt19937_64 mt(chrono::steady_clock::now().time_since_epoch().count());

int system_const;
namespace fast_IO{
    const int maxbuff = 1<<17;
    char ibuff[maxbuff],obuff[maxbuff];
    inline char Getchar(){
        static char *p1=ibuff,*p2=ibuff;
        if(p1 == p2) {
            p1 = ibuff;
            p2 = ibuff+fread(ibuff,1,maxbuff,stdin);
            if(p1 == p2) return -1;
        }
        return *p1++;
    }

    int optr = 0;
    inline void Putchar(char c) {
        if(optr == maxbuff) {fwrite(obuff,1,optr,stdout);optr = 0;}
        obuff[optr++] = c;
    }

    inline void Flush() {
        fwrite(obuff,1,optr,stdout);
    }

    template<class T>
    void read_number(T &x) {
        x = 0;
        char c;int f = 0;
        for(c = Getchar() ; c != '-' && (c < '0' || c > '9') ; c = Getchar());
        if(c == '-') f = 1;
        else x = c-'0';
        for(c = Getchar() ; c >= '0' && c <= '9' ; c = Getchar()) x = x*10+c-'0';
        if(f) x = -x;
    }

    void read_string(string &s) {
        s.clear();
        char c;
        for(c = Getchar() ; c == ' ' || c == '\n' ; c = Getchar());
        s.push_back(c);
        for(c = Getchar() ; c != ' ' && c != '\n' ; c = Getchar())
            s.push_back(c);
    }

    template<class T>
    void write_number(T &x) {
        static int dec[20];int len = 0;
        if(x == 0) {Putchar('0');return ;}
        if(x < 0) {Putchar('-');x = -x;}
        while(x) {
            dec[++len] = x%10;
            x /= 10;
        }
        while(len) Putchar(dec[len--]+'0');
    }

    void write_string(const string &s) {
        for(int i = 0 ;i < s.size() ; ++i)
            Putchar(s[i]);
    }
}
void read(string &s) {
    if(system_const == 0) {
        using namespace fast_IO;
        read_string(s);
    }
    else cin >> s;
}
template<class T> void read(T &x) {
    if(system_const == 0) {
        using namespace fast_IO;
        read_number(x);
    }
    else cin >> x;
}
void write(const string &s) {
    if(system_const == 0) {
        fast_IO::write_string(s);
    }
    else cout << s;
}
template<class T> void write(T &x) {
    if(system_const == 0) {
        fast_IO::write_number(x);
    }
    else cout << x;
}
void writeln(const string &s) {
    write(s);
    if(system_const == 0) {
        using namespace fast_IO;
        Putchar('\n');
    }
    else cout << endl;
}
template<class T> void writeln(T x) {
    write(x);
    if(system_const == 0) {
        using namespace fast_IO;
        Putchar('\n');
    }
    else cout << '\n';
}
void writesp(const string &s) {
    write(s);
    if(system_const == 0) {
        using namespace fast_IO;
        Putchar(' ');
    }
    else cout << ' ';
}

template<class T> void writesp(T x) {
    write(x);
    if(system_const == 0) {
        using namespace fast_IO;
        Putchar(' ');
    }
    else cout << ' ';
}
void set_program_type(int t = 0) {
    system_const = t;
    if(t) {
        ios::sync_with_stdio(0);
        cin.tie(0);cout.tie(0);
    }
}
void finish_program(int t = 0) {
    if(!t) fast_IO::Flush();
}

void init_all(int t = 0) {
#ifdef LOCAL
    if ((t >> 1) & 1) freopen("input", "r", stdin);
    if ((t >> 2) & 1) freopen("output", "w", stdout);
#endif
}

int pre_ask_time;
void show_time() {
    int now = clock();
    cout << now-pre_ask_time << endl;
    pre_ask_time = now;
}

template<class T> void cmin(T &x,const T&y) {if(x > y) x = y;}

const int NN = 4e5+19,N = 1000+19,M = 1<<15,D = 14;
LL inf = 1e17+7;
int nxt[N][D],fa[N];
int n,tot,jmp[N],m;
LL f[N][M],w[N],det[N];
char s[NN];
vector<int> bit[D+1];
int vis[M];
void solve() {
    bit[0].push_back(0);

    rep(i,1,D) {
        for(auto v : bit[i-1]) rep(j,0,D-1)
            if(~v>>j&1) if(!vis[v|(1<<j)]) {
                bit[i].push_back(v|(1<<j));
                vis[v|(1<<j)] = 1;
            }
    }

    scanf("%d",&m);
    rep(i,1,m) {
        int x;
        scanf("%s%d",s+1,&x);
        int cur = 0,len = strlen(s+1);
        rep(j,1,len) {
            if(!nxt[cur][s[j]-'a']) nxt[cur][s[j]-'a'] = ++tot;
            cur = nxt[cur][s[j]-'a'];
        }
        w[cur] += x;
    }

    {
        queue<int> q;
        q.push(0);
        while(!q.empty()) {
            int cur = q.front();q.pop();
            rep(i,0,D-1) if(nxt[cur][i]) {
                int p = nxt[cur][i];
                fa[p] = cur?nxt[fa[cur]][i]:0;
                q.push(p);
                w[p] += w[fa[p]];
            }
            else nxt[cur][i] = nxt[fa[cur]][i];
        }
    }

    scanf("%s",s+1);
    n = strlen(s+1);

    rep(i,0,tot) {
        jmp[i] = i;
        det[i] = 0;
        rep(j,0,M-1) f[i][j] = -inf;
    }
    f[0][0] = 0;

    int dep = 0;
    rep(i,1,n) if(s[i] == '?') {
        rep(j,0,tot) for(auto mask : bit[dep]) if(f[j][mask] != -inf) {
            rep(c,0,D-1) if(~mask>>c&1) {
                int q = nxt[jmp[j]][c];
                f[q][mask|(1<<c)] = max(f[q][mask|(1<<c)],f[j][mask]+w[q]+det[j]);
            }
        }
        rep(j,0,tot) {
            jmp[j] = j;
            det[j] = 0;
        }
        ++dep;
    }
    else {
        rep(j,0,tot) {
            jmp[j] = nxt[jmp[j]][s[i]-'a'];
            det[j] += w[jmp[j]];
        }
    }

    LL ans = -inf;
    rep(j,0,tot) for(auto v : bit[dep])
        ans = max(ans,f[j][v]+det[j]);
    writeln(ans);
}

signed main() {
    //srand((int)time(0));
    pre_ask_time = clock();
    init_all(3);
    //init();
    set_program_type(0);

    int T = 1;
    //read(T);
    //scanf("%d",&T);
    rep(i,1,T) {
        //printf("Case #%d: ",i);
        solve();
    }

    finish_program(0);

    return 0;
}