#include<bits/stdc++.h>
#include<ext/pb_ds/assoc_container.hpp>
#include<ext/pb_ds/tree_policy.hpp>
using namespace std;
using namespace chrono;
using namespace __gnu_pbds;
mt19937 rng(high_resolution_clock::now().time_since_epoch().count());
mt19937_64 rngll(high_resolution_clock::now().time_since_epoch().count());
template<typename T> T ctmax(T &x, const T &y){ return x = max(x, y); }
template<typename T> T ctmin(T &x, const T &y){ return x = min(x, y); }
template<typename T> using Tree = tree<T, null_type, less<T>, rb_tree_tag, tree_order_statistics_node_update>;

template<typename Str>
struct aho_corasic{
	typedef typename Str::value_type Char;
	struct node{
		int par, link = -1, elink = -1;
		Char cpar;
		map<Char, int> next, go;
		bool isleaf = false;
		node(int par = -1, Char pch = '$'): par(par), cpar(pch){ }
		long long val = 0;
	};
	vector<node> state = vector<node>(1);
	int insert(const Str &s){
		int u = 0;
		for(auto &c: s){
			if(!state[u].next.count(c)){
				state[u].next[c] = int(state.size());
				state.emplace_back(u, c);
			}
			u = state[u].next[c];
		}
		state[u].isleaf = true;
		return u;
	}
	int get_link(int u){
		if(state[u].link == -1){
			if(!u || !state[u].par) state[u].link = 0;
			else state[u].link = go(get_link(state[u].par), state[u].cpar);
		}
		return state[u].link;
	}
	int get_exit_link(int u){
		if(state[u].elink == -1){
			if(!u || !get_link(u)) state[u].elink = 0;
			else if(state[get_link(u)].isleaf) state[u].elink = get_link(u);
			else state[u].elink = get_exit_link(get_link(u));
		}
		return state[u].elink;
	}
	int go(int u, Char c){
		if(!state[u].go.count(c)){
			if(state[u].next.count(c)) state[u].go[c] = state[u].next[c];
			else state[u].go[c] = u ? go(get_link(u), c) : u;
		}
		return state[u].go[c];
	}
	void print(int u, Str s = ""){
		cout << "Node " << u << ": par = " << state[u].par << ", cpar = " << state[u].cpar << ", string: " << s << "\n";
		for(auto &[c, v]: state[u].next){
			cout << u << " => ";
			print(v, s + Str{c});
		}
	}
};

int main(){
	cin.tie(0)->sync_with_stdio(0);
	aho_corasic<string> aut;
	int k;
	cin >> k;
	for(int i = 0; i < k; ++ i){
		string t;
		int x;
		cin >> t >> x;
		int u = aut.insert(t);
		aut.state[u].val += x;
	}
	int n = int(aut.state.size());
	for(int u = 0; u < n; ++ u){
		aut.state[u].val += aut.state[aut.get_exit_link(u)].val;
	}
	string s;
	cin >> s;
	const int mx = 14;
	const long long minf = numeric_limits<long long>::min() / 8;
	vector<vector<long long>> dp(n, vector<long long>(1 << mx, minf));
	dp[0][0] = 0;
	for(int i = 0; i < int(s.size()); ){
		int j = i;
		while(j < int(s.size()) && s[j] != '?'){
			++ j;
		}
		static vector<vector<long long>> dp_next(n, vector<long long>(1 << mx));
		auto init_next = [&](){
			for(int i = 0; i < n; ++ i){
				for(int j = 0; j < 1 << mx; ++ j){
					dp_next[i][j] = minf;
				}
			}
		};
		init_next();
		for(int u = 0; u < n; ++ u){
			long long sum = 0;
			int v = u;
			for(int k = i; k < j; ++ k){
				v = aut.go(v, s[k]);
				sum += aut.state[v].val;
			}
			for(int mask = 0; mask < 1 << mx; ++ mask){
				if(dp[u][mask] != minf){
					ctmax(dp_next[v][mask], dp[u][mask] + sum);
				}
			}
		}
		swap(dp, dp_next);
		if(j < int(s.size())){
			init_next();
			for(int mask = 0; mask < 1 << mx; ++ mask){
				for(int bit = 0; bit < mx; ++ bit){
					if(!(mask & 1 << bit)){
						int vmask = mask | 1 << bit;
						for(int u = 0; u < n; ++ u){
							if(dp[u][mask] != minf){
								int v = aut.go(u, 'a' + bit);
								ctmax(dp_next[v][vmask], dp[u][mask] + aut.state[v].val);
							}
						}
					}
				}
			}
			swap(dp, dp_next);
		}
		i = j + 1;
	}
	long long res = minf;
	for(int u = 0; u < n; ++ u){
		ctmax(res, *max_element(dp[u].begin(), dp[u].end()));
	}
	cout << res;
	return 0;
}

/*

*/

////////////////////////////////////////////////////////////////////////////////////////
//                                                                                //
//                                   Coded by Aeren                                   //
//                                                                                //
////////////////////////////////////////////////////////////////////////////////////////


#include<bits/stdc++.h>
#include<ext/pb_ds/assoc_container.hpp>
#include<ext/pb_ds/tree_policy.hpp>
using namespace std;
using namespace chrono;
using namespace __gnu_pbds;
mt19937 rng(high_resolution_clock::now().time_since_epoch().count());
mt19937_64 rngll(high_resolution_clock::now().time_since_epoch().count());
template<typename T> T ctmax(T &x, const T &y){ return x = max(x, y); }
template<typename T> T ctmin(T &x, const T &y){ return x = min(x, y); }
template<typename T> using Tree = tree<T, null_type, less<T>, rb_tree_tag, tree_order_statistics_node_update>;

template<typename Str, int lim = 128>
struct aho_corasic{
	typedef typename Str::value_type Char;
	struct node{
		int par, link = -1, elink = -1;
		Char cpar;
		vector<int> next, go;
		bool isleaf = false;
		node(int par = -1, Char pch = '$'): par(par), cpar(pch), next(128, -1), go(128, -1){ }
		long long val = 0;
		bool mark = false;
	};
	vector<node> state = vector<node>(1);
	int insert(const Str &s){
		int u = 0;
		for(auto &c: s){
			if(state[u].next[c] == -1){
				state[u].next[c] = int(state.size());
				state.emplace_back(u, c);
			}
			u = state[u].next[c];
		}
		state[u].isleaf = true;
		return u;
	}
	int get_link(int u){
		if(state[u].link == -1){
			if(!u || !state[u].par) state[u].link = 0;
			else state[u].link = go(get_link(state[u].par), state[u].cpar);
		}
		return state[u].link;
	}
	int get_elink(int u){
		if(state[u].elink == -1){
			if(!u || !get_link(u)) state[u].elink = 0;
			else if(state[get_link(u)].isleaf) state[u].elink = get_link(u);
			else state[u].elink = get_elink(get_link(u));
		}
		return state[u].elink;
	}
	int go(int u, Char c){
		if(state[u].go[c] == -1){
			if(state[u].next[c] != -1) state[u].go[c] = state[u].next[c];
			else state[u].go[c] = u ? go(get_link(u), c) : u;
		}
		return state[u].go[c];
	}
	void print(int u, Str s = ""){
		cout << "Node " << u << ": par = " << state[u].par << ", cpar = " << state[u].cpar << ", string: " << s << "\n";
		for(int c = 0; c < lim; ++ c){
			if(state[u].next[c] != -1){
				cout << u << " => ";
				print(state[u].next[c], s + Str{c});
			}
		}
	}
};

int main(){
	cin.tie(0)->sync_with_stdio(0);
	aho_corasic<string> aut;
	int k;
	cin >> k;
	for(int i = 0; i < k; ++ i){
		string t;
		int x;
		cin >> t >> x;
		int u = aut.insert(t);
		aut.state[u].val += x;
	}
	int n = int(aut.state.size());
	function<long long(int)> process = [&](int u){
		if(aut.state[u].mark){
			return aut.state[u].val;
		}
		aut.state[u].mark = true;
		return aut.state[u].val += process(aut.get_elink(u));
	};
	for(int u = 0; u < n; ++ u){
		if(!aut.state[u].mark){
			process(u);
		}
	}
	const int mx = 14;
	string s;
	cin >> s;
	vector<vector<int>> masks(mx + 1);
	for(int mask = 0; mask < 1 << mx; ++ mask){
		masks[__builtin_popcount(mask)].push_back(mask);
	}
	const long long minf = numeric_limits<long long>::min() / 8;
	vector<vector<long long>> dp(n, vector<long long>(1 << mx, minf));
	dp[0][0] = 0;
	for(int i = 0; i < int(s.size()); ){
		static int cur_bit = 0;
		int j = i;
		while(j < int(s.size()) && s[j] != '?'){
			++ j;
		}
		static vector<vector<long long>> dp_next(n, vector<long long>(1 << mx));
		auto init_next = [&](){
			for(int i = 0; i < n; ++ i){
				for(int j = 0; j < 1 << mx; ++ j){
					dp_next[i][j] = minf;
				}
			}
		};
		init_next();
		for(int u = 0; u < n; ++ u){
			long long sum = 0;
			int v = u;
			for(int k = i; k < j; ++ k){
				v = aut.go(v, s[k]);
				sum += aut.state[v].val;
			}
			for(auto mask: masks[cur_bit]){
				if(dp[u][mask] != minf){
					ctmax(dp_next[v][mask], dp[u][mask] + sum);
				}
			}
		}
		swap(dp, dp_next);
		if(j < int(s.size())){
			init_next();
			for(auto mask: masks[cur_bit]){
				for(int bit = 0; bit < mx; ++ bit){
					if(!(mask & 1 << bit)){
						int vmask = mask | 1 << bit;
						for(int u = 0; u < n; ++ u){
							if(dp[u][mask] != minf){
								int v = aut.go(u, 'a' + bit);
								ctmax(dp_next[v][vmask], dp[u][mask] + aut.state[v].val);
							}
						}
					}
				}
			}
			swap(dp, dp_next);
			++ cur_bit;
		}
		i = j + 1;
	}
	long long res = minf;
	for(int u = 0; u < n; ++ u){
		ctmax(res, *max_element(dp[u].begin(), dp[u].end()));
	}
	cout << res;
	return 0;
}

/*

*/

////////////////////////////////////////////////////////////////////////////////////////
//                                                                                //
//                                   Coded by Aeren                                   //
//                                                                                //
////////////////////////////////////////////////////////////////////////////////////////


#include<bits/stdc++.h>
#include<ext/pb_ds/assoc_container.hpp>
#include<ext/pb_ds/tree_policy.hpp>
using namespace std;
using namespace chrono;
using namespace __gnu_pbds;
mt19937 rng(high_resolution_clock::now().time_since_epoch().count());
mt19937_64 rngll(high_resolution_clock::now().time_since_epoch().count());
template<typename T> T ctmax(T &x, const T &y){ return x = max(x, y); }
template<typename T> T ctmin(T &x, const T &y){ return x = min(x, y); }
template<typename T> using Tree = tree<T, null_type, less<T>, rb_tree_tag, tree_order_statistics_node_update>;

template<typename Str, int lim = 128>
struct aho_corasic{
	typedef typename Str::value_type Char;
	struct node{
		int par, link = -1, elink = -1;
		Char cpar;
		vector<int> next, go;
		bool isleaf = false;
		node(int par = -1, Char pch = '$'): par(par), cpar(pch), next(128, -1), go(128, -1){ }
		long long val = 0;
	};
	vector<node> state = vector<node>(1);
	int insert(const Str &s){
		int u = 0;
		for(auto &c: s){
			if(state[u].next[c] == -1){
				state[u].next[c] = int(state.size());
				state.emplace_back(u, c);
			}
			u = state[u].next[c];
		}
		state[u].isleaf = true;
		return u;
	}
	int get_link(int u){
		if(state[u].link == -1){
			if(!u || !state[u].par) state[u].link = 0;
			else state[u].link = go(get_link(state[u].par), state[u].cpar);
		}
		return state[u].link;
	}
	int get_elink(int u){
		if(state[u].elink == -1){
			if(!u || !get_link(u)) state[u].elink = 0;
			else if(state[get_link(u)].isleaf) state[u].elink = get_link(u);
			else state[u].elink = get_elink(get_link(u));
		}
		return state[u].elink;
	}
	int go(int u, Char c){
		if(state[u].go[c] == -1){
			if(state[u].next[c] != -1) state[u].go[c] = state[u].next[c];
			else state[u].go[c] = u ? go(get_link(u), c) : u;
		}
		return state[u].go[c];
	}
	void print(int u, Str s = ""){
		cout << "Node " << u << ": par = " << state[u].par << ", cpar = " << state[u].cpar << ", string: " << s << "\n";
		for(int c = 0; c < lim; ++ c){
			if(state[u].next[c] != -1){
				cout << u << " => ";
				print(state[u].next[c], s + Str{c});
			}
		}
	}
};

int main(){
	cin.tie(0)->sync_with_stdio(0);
	aho_corasic<string> aut;
	int k;
	cin >> k;
	for(int i = 0; i < k; ++ i){
		string t;
		int x;
		cin >> t >> x;
		int u = aut.insert(t);
		aut.state[u].val += x;
	}
	int n = int(aut.state.size());
	for(int u = 0; u < n; ++ u){
		aut.state[u].val += aut.state[aut.get_elink(u)].val;
	}
	string s;
	cin >> s;
	const int mx = 14;
	const long long minf = numeric_limits<long long>::min() / 8;
	vector<vector<long long>> dp(n, vector<long long>(1 << mx, minf));
	dp[0][0] = 0;
	for(int i = 0; i < int(s.size()); ){
		int j = i;
		while(j < int(s.size()) && s[j] != '?'){
			++ j;
		}
		static vector<vector<long long>> dp_next(n, vector<long long>(1 << mx));
		auto init_next = [&](){
			for(int i = 0; i < n; ++ i){
				for(int j = 0; j < 1 << mx; ++ j){
					dp_next[i][j] = minf;
				}
			}
		};
		init_next();
		for(int u = 0; u < n; ++ u){
			long long sum = 0;
			int v = u;
			for(int k = i; k < j; ++ k){
				v = aut.go(v, s[k]);
				sum += aut.state[v].val;
			}
			for(int mask = 0; mask < 1 << mx; ++ mask){
				if(dp[u][mask] != minf){
					ctmax(dp_next[v][mask], dp[u][mask] + sum);
				}
			}
		}
		swap(dp, dp_next);
		if(j < int(s.size())){
			init_next();
			for(int mask = 0; mask < 1 << mx; ++ mask){
				for(int bit = 0; bit < mx; ++ bit){
					if(!(mask & 1 << bit)){
						int vmask = mask | 1 << bit;
						for(int u = 0; u < n; ++ u){
							if(dp[u][mask] != minf){
								int v = aut.go(u, 'a' + bit);
								ctmax(dp_next[v][vmask], dp[u][mask] + aut.state[v].val);
							}
						}
					}
				}
			}
			swap(dp, dp_next);
		}
		i = j + 1;
	}
	long long res = minf;
	for(int u = 0; u < n; ++ u){
		ctmax(res, *max_element(dp[u].begin(), dp[u].end()));
	}
	cout << res;
	return 0;
}

/*

*/

////////////////////////////////////////////////////////////////////////////////////////
//                                                                                //
//                                   Coded by Aeren                                   //
//                                                                                //
////////////////////////////////////////////////////////////////////////////////////////


#include<bits/stdc++.h>
using namespace std;
#define IOS std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
#define debug(...) " [" << #__VA_ARGS__ ": " << (__VA_ARGS__) << "] "
#define IN freopen("in.txt", "r", stdin);
#define endl '\n'
#define pb push_back
#define eb emplace_back
#define fi first
#define se second
#define all(x) x.begin(),x.end()
#define sz(x) (int)x.size()
typedef long long ll;
typedef vector<int> VI;
typedef pair<int, int> pii;
const ll inf = 0x3f3f3f3f3f3f3f3f;

const int N = 4e5+10;
const int M = 1100;
char s[N];
ll dp[1100][20000];
VI v[20];

namespace AC{
    int tr[M][26],tot;
    ll val[M],pointval[M],add[M];
    int e[M],fail[M],to[M];
    void init(){
        memset(tr[0],0,sizeof tr[0]);
        e[0]=0;
        tot=0;
    }
    void insert(char *s,int v){
        int u=0,len=strlen(s);
        for(int i=0;i<len;i++){
            int c=s[i]-'a';
            if(!tr[u][c]){
                memset(tr[++tot],0,sizeof tr[tot]);
                e[tot]=0;
                tr[u][c]=tot;
            }
            u=tr[u][c];
        }
        e[u]=1;
        val[u]+=v;
    }
    void build(){
        queue<int> q;
        fail[0]=0;
        for(int i=0;i<26;i++)
            if(tr[0][i]){
                fail[tr[0][i]]=0;
                q.push(tr[0][i]);
            }
        while(!q.empty()){
            int u=q.front();q.pop();
            for(int i=0;i<26;i++){
                if(tr[u][i])
                    fail[tr[u][i]]=tr[fail[u]][i],
                    q.push(tr[u][i]);
                else
                    tr[u][i]=tr[fail[u]][i];
            }
        }
    }
    void Pre(int u){
        for(int i=u;i;i=fail[i])pointval[u]+=val[i];
    }
    ll solve(char *s){
        int len=strlen(s),c=0;
        for(int i=1;i<=tot;i++)Pre(i);
        for(int i=0;i<=tot;i++)fill(dp[i],dp[i]+(1<<15),-inf);
        dp[0][0]=0;
        for(int i=0;i<=tot;i++)to[i]=i;
        for(int po=0;po<len;po++){
            if(s[po]=='?'){
                c++;
                for(int i=0;i<=tot;i++)
                    for(auto it:v[c-1]){
                        if(dp[i][it]==-inf)continue;
                        for(int j=0;j<14;j++){
                            if(it&(1<<j))continue;
                            dp[tr[to[i]][j]][it|(1<<j)]=max(dp[tr[to[i]][j]][it|(1<<j)],dp[i][it]+pointval[tr[to[i]][j]]+add[i]);
                        }
                    }
                for(int i=0;i<=tot;i++)to[i]=i;
                memset(add,0,sizeof add);
            }else{
                int c=s[po]-'a';
                for(int i=0;i<=tot;i++){
                    to[i]=tr[to[i]][c];
                    add[i]+=pointval[to[i]];
                }
            }
        }
        ll res=-inf;
        for(int i=0;i<=tot;i++)for(auto j:v[c])res=max(res,dp[i][j]+add[i]);
        return res;
    }
}

int main(){
    IOS;
    int n,val; cin>>n;
    AC::init();
    for(int i=0;i<n;i++)cin>>s>>val,AC::insert(s,val);
    AC::build();
    for(int i=0;i<(1<<14);i++){
        int c=0;
        for(int j=0;j<14;j++)if(i&(1<<j))c++;
        v[c].pb(i);
    }
    cin>>s;
    cout<<AC::solve(s);
    return 0;
}


#include<bits/stdc++.h>
using namespace std;
#define IOS std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
#define debug(...) " [" << #__VA_ARGS__ ": " << (__VA_ARGS__) << "] "
#define IN freopen("in.txt", "r", stdin);
#define endl '\n'
#define pb push_back
#define eb emplace_back
#define fi first
#define se second
#define all(x) x.begin(),x.end()
#define sz(x) (int)x.size()
typedef long long ll;
typedef vector<int> VI;
typedef pair<int, int> pii;
const ll inf = 0x3f3f3f3f3f3f3f3f;

const int N = 4e5+10;
const int M = 1100;
char s[N];
ll dp[1100][20000];
VI v[20];

namespace AC{
    int tr[M][26],tot;
    ll val[M],pointval[M],add[M];
    int e[M],fail[M],to[M];
    void init(){
        memset(tr[0],0,sizeof tr[0]);
        e[0]=0;
        tot=0;
    }
    void insert(char *s,int v){
        int u=0,len=strlen(s);
        for(int i=0;i<len;i++){
            int c=s[i]-'a';
            if(!tr[u][c]){
                memset(tr[++tot],0,sizeof tr[tot]);
                e[tot]=0;
                tr[u][c]=tot;
            }
            u=tr[u][c];
        }
        e[u]=1;
        val[u]+=v;
    }
    void build(){
        queue<int> q;
        fail[0]=0;
        for(int i=0;i<26;i++)
            if(tr[0][i]){
                fail[tr[0][i]]=0;
                q.push(tr[0][i]);
            }
        while(!q.empty()){
            int u=q.front();q.pop();
            for(int i=0;i<26;i++){
                if(tr[u][i])
                    fail[tr[u][i]]=tr[fail[u]][i],
                    q.push(tr[u][i]);
                else
                    tr[u][i]=tr[fail[u]][i];
            }
        }
    }
    void Pre(int u){
        for(int i=u;i;i=fail[i])pointval[u]+=val[i];
    }
    int solve(char *s){
        int len=strlen(s),c=0;
        for(int i=1;i<=tot;i++)Pre(i);
        for(int i=0;i<=tot;i++)fill(dp[i],dp[i]+(1<<15),-inf);
        dp[0][0]=0;
        for(int i=0;i<=tot;i++)to[i]=i;
        for(int po=0;po<len;po++){
            if(s[po]=='?'){
                c++;
                for(int i=0;i<=tot;i++)
                    for(auto it:v[c-1]){
                        if(dp[i][it]==-inf)continue;
                        for(int j=0;j<14;j++){
                            if(it&(1<<j))continue;
                            dp[tr[to[i]][j]][it|(1<<j)]=max(dp[tr[to[i]][j]][it|(1<<j)],dp[i][it]+pointval[tr[to[i]][j]]+add[i]);
                        }
                    }
                for(int i=0;i<=tot;i++)to[i]=i;
                memset(add,0,sizeof add);
            }else{
                int c=s[po]-'a';
                for(int i=0;i<=tot;i++){
                    to[i]=tr[to[i]][c];
                    add[i]+=pointval[to[i]];
                }
            }
        }
        ll res=-inf;
        for(int i=0;i<=tot;i++)for(auto j:v[c])res=max(res,dp[i][j]+add[i]);
        return res;
    }
}

int main(){
    IOS;
    int n,val; cin>>n;
    AC::init();
    for(int i=0;i<n;i++)cin>>s>>val,AC::insert(s,val);
    AC::build();
    for(int i=0;i<(1<<14);i++){
        int c=0;
        for(int j=0;j<14;j++)if(i&(1<<j))c++;
        v[c].pb(i);
    }
    cin>>s;
    cout<<AC::solve(s);
    return 0;
}


#include<bits/stdc++.h>
using namespace std;
#define IOS std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
#define debug(...) " [" << #__VA_ARGS__ ": " << (__VA_ARGS__) << "] "
#define IN freopen("in.txt", "r", stdin);
#define endl '\n'
#define pb push_back
#define eb emplace_back
#define fi first
#define se second
#define all(x) x.begin(),x.end()
#define sz(x) (int)x.size()
typedef long long ll;
typedef vector<int> VI;
typedef pair<int, int> pii;
const int inf = 0x3f3f3f3f;

const int N = 4e5+10;
const int M = 1100;
char s[N];
ll dp[1100][N];
VI v[20];

namespace AC{
    int tr[M][26],tot,val[M],pointval[M],add[M];
    int e[M],fail[M],to[M];
    void init(){
        memset(tr[0],0,sizeof tr[0]);
        e[0]=0;
        tot=0;
    }
    void insert(char *s,int v){
        int u=0,len=strlen(s);
        for(int i=0;i<len;i++){
            int c=s[i]-'a';
            if(!tr[u][c]){
                memset(tr[++tot],0,sizeof tr[tot]);
                e[tot]=0;
                tr[u][c]=tot;
            }
            u=tr[u][c];
        }
        e[u]=1;
        val[u]+=v;
    }
    void build(){
        queue<int> q;
        fail[0]=0;
        for(int i=0;i<26;i++)
            if(tr[0][i]){
                fail[tr[0][i]]=0;
                q.push(tr[0][i]);
            }
        while(!q.empty()){
            int u=q.front();q.pop();
            for(int i=0;i<26;i++){
                if(tr[u][i])
                    fail[tr[u][i]]=tr[fail[u]][i],
                    q.push(tr[u][i]);
                else
                    tr[u][i]=tr[fail[u]][i];
            }
        }
    }
    void Pre(int u){
        for(int i=u;i;i=fail[i])pointval[u]+=val[i];
    }
    int solve(char *s){
        int len=strlen(s),c=0;
        for(int i=1;i<=tot;i++)Pre(i);
        for(int i=0;i<=tot;i++)fill(dp[i],dp[i]+(1<<15),-inf);
        dp[0][0]=0;
        for(int i=0;i<=tot;i++)to[i]=i;
        for(int po=0;po<len;po++){
            if(s[po]=='?'){
                c++;
                for(int i=0;i<=tot;i++)
                    for(auto it:v[c-1]){
                        if(dp[i][it]==-inf)continue;
                        for(int j=0;j<14;j++){
                            if(it&(1<<j))continue;
                            dp[tr[to[i]][j]][it|(1<<j)]=max(dp[tr[to[i]][j]][it|(1<<j)],dp[i][it]+pointval[tr[to[i]][j]]+add[i]);
                        }
                    }
                for(int i=0;i<=tot;i++)to[i]=i;
                memset(add,0,sizeof add);
            }else{
                int c=s[po]-'a';
                for(int i=0;i<=tot;i++){
                    to[i]=tr[to[i]][c];
                    add[i]+=pointval[to[i]];
                }
            }
        }
        ll res=-inf;
        for(int i=0;i<=tot;i++)for(auto j:v[c])res=max(res,dp[i][j]+add[i]);
        return res;
    }
}

int main(){
    IOS;
    int n,val; cin>>n;
    AC::init();
    for(int i=0;i<n;i++)cin>>s>>val,AC::insert(s,val);
    AC::build();
    for(int i=0;i<(1<<14);i++){
        int c=0;
        for(int j=0;j<14;j++)if(i&(1<<j))c++;
        v[c].pb(i);
    }
    cin>>s;
    cout<<AC::solve(s);
    return 0;
}


#include<bits/stdc++.h>
using namespace std;
#define IOS std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
#define debug(...) " [" << #__VA_ARGS__ ": " << (__VA_ARGS__) << "] "
#define IN freopen("in.txt", "r", stdin);
#define endl '\n'
#define pb push_back
#define eb emplace_back
#define fi first
#define se second
#define all(x) x.begin(),x.end()
#define sz(x) (int)x.size()
typedef long long ll;
typedef vector<int> VI;
typedef pair<int, int> pii;
const int inf = 0x3f3f3f3f;

const int N = 4e5+10;
const int M = 1100;
char s[N];
ll dp[1100][20000];
VI v[20];

namespace AC{
    int tr[M][26],tot,val[M],pointval[M],add[M];
    int e[M],fail[M],to[M];
    void init(){
        memset(tr[0],0,sizeof tr[0]);
        e[0]=0;
        tot=0;
    }
    void insert(char *s,int v){
        int u=0,len=strlen(s);
        for(int i=0;i<len;i++){
            int c=s[i]-'a';
            if(!tr[u][c]){
                memset(tr[++tot],0,sizeof tr[tot]);
                e[tot]=0;
                tr[u][c]=tot;
            }
            u=tr[u][c];
        }
        e[u]=1;
        val[u]+=v;
    }
    void build(){
        queue<int> q;
        fail[0]=0;
        for(int i=0;i<26;i++)
            if(tr[0][i]){
                fail[tr[0][i]]=0;
                q.push(tr[0][i]);
            }
        while(!q.empty()){
            int u=q.front();q.pop();
            for(int i=0;i<26;i++){
                if(tr[u][i])
                    fail[tr[u][i]]=tr[fail[u]][i],
                    q.push(tr[u][i]);
                else
                    tr[u][i]=tr[fail[u]][i];
            }
        }
    }
    void Pre(int u){
        for(int i=u;i;i=fail[i])pointval[u]+=val[i];
    }
    int solve(char *s){
        int len=strlen(s),c=0;
        for(int i=1;i<=tot;i++)Pre(i);
        for(int i=0;i<=tot;i++)fill(dp[i],dp[i]+(1<<15),-inf);
        dp[0][0]=0;
        for(int i=0;i<=tot;i++)to[i]=i;
        for(int po=0;po<len;po++){
            if(s[po]=='?'){
                c++;
                for(int i=0;i<=tot;i++)
                    for(auto it:v[c-1]){
                        if(dp[i][it]==-inf)continue;
                        for(int j=0;j<14;j++){
                            if(it&(1<<j))continue;
                            dp[tr[to[i]][j]][it|(1<<j)]=max(dp[tr[to[i]][j]][it|(1<<j)],dp[i][it]+pointval[tr[to[i]][j]]+add[i]);
                        }
                    }
                for(int i=0;i<=tot;i++)to[i]=i;
                memset(add,0,sizeof add);
            }else{
                int c=s[po]-'a';
                for(int i=0;i<=tot;i++){
                    to[i]=tr[to[i]][c];
                    add[i]+=pointval[to[i]];
                }
            }
        }
        ll res=-inf;
        for(int i=0;i<=tot;i++)for(auto j:v[c])res=max(res,dp[i][j]+add[i]);
        return res;
    }
}

int main(){
    IOS;
    int n,val; cin>>n;
    AC::init();
    for(int i=0;i<n;i++)cin>>s>>val,AC::insert(s,val);
    AC::build();
    for(int i=0;i<(1<<14);i++){
        int c=0;
        for(int j=0;j<14;j++)if(i&(1<<j))c++;
        v[c].pb(i);
    }
    cin>>s;
    cout<<AC::solve(s);
    return 0;
}


#include<bits/stdc++.h>
using namespace std;
#define IOS std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
#define debug(...) " [" << #__VA_ARGS__ ": " << (__VA_ARGS__) << "] "
#define IN freopen("in.txt", "r", stdin);
#define endl '\n'
#define pb push_back
#define eb emplace_back
#define int long long
#define fi first
#define se second
#define all(x) x.begin(),x.end()
#define sz(x) (int)x.size()
typedef long long ll;
typedef vector<int> VI;
typedef pair<int, int> pii;
const ll inf = 0x3f3f3f3f3f3f3f3f;

const int N = 4e5+10;
const int M = 1100;
char s[N];
ll dp[1100][20000];
VI v[20];

namespace AC{
    int tr[M][26],tot;
    ll val[M],pointval[M],add[M];
    int e[M],fail[M],to[M];
    void init(){
        memset(tr[0],0,sizeof tr[0]);
        e[0]=0;
        tot=0;
    }
    void insert(char *s,int v){
        int u=0,len=strlen(s);
        for(int i=0;i<len;i++){
            int c=s[i]-'a';
            if(!tr[u][c]){
                memset(tr[++tot],0,sizeof tr[tot]);
                e[tot]=0;
                tr[u][c]=tot;
            }
            u=tr[u][c];
        }
        e[u]=1;
        val[u]+=v;
    }
    void build(){
        queue<int> q;
        fail[0]=0;
        for(int i=0;i<26;i++)
            if(tr[0][i]){
                fail[tr[0][i]]=0;
                q.push(tr[0][i]);
            }
        while(!q.empty()){
            int u=q.front();q.pop();
            for(int i=0;i<26;i++){
                if(tr[u][i])
                    fail[tr[u][i]]=tr[fail[u]][i],
                    q.push(tr[u][i]);
                else
                    tr[u][i]=tr[fail[u]][i];
            }
        }
    }
    void Pre(int u){
        for(int i=u;i;i=fail[i])pointval[u]+=val[i];
    }
    int solve(char *s){
        int len=strlen(s),c=0;
        for(int i=1;i<=tot;i++)Pre(i);
        for(int i=0;i<=tot;i++)fill(dp[i],dp[i]+(1<<15),-inf);
        dp[0][0]=0;
        for(int i=0;i<=tot;i++)to[i]=i;
        for(int po=0;po<len;po++){
            if(s[po]=='?'){
                c++;
                for(int i=0;i<=tot;i++)
                    for(auto it:v[c-1]){
                        if(dp[i][it]==-inf)continue;
                        for(int j=0;j<14;j++){
                            if(it&(1<<j))continue;
                            dp[tr[to[i]][j]][it|(1<<j)]=max(dp[tr[to[i]][j]][it|(1<<j)],dp[i][it]+pointval[tr[to[i]][j]]+add[i]);
                        }
                    }
                for(int i=0;i<=tot;i++)to[i]=i;
                memset(add,0,sizeof add);
            }else{
                int c=s[po]-'a';
                for(int i=0;i<=tot;i++){
                    to[i]=tr[to[i]][c];
                    add[i]+=pointval[to[i]];
                }
            }
        }
        ll res=-inf;
        for(int i=0;i<=tot;i++)for(auto j:v[c])res=max(res,dp[i][j]+add[i]);
        return res;
    }
}

signed main(){
    IOS;
    int n,val; cin>>n;
    AC::init();
    for(int i=0;i<n;i++)cin>>s>>val,AC::insert(s,val);
    AC::build();
    for(int i=0;i<(1<<14);i++){
        int c=0;
        for(int j=0;j<14;j++)if(i&(1<<j))c++;
        v[c].pb(i);
    }
    cin>>s;
    cout<<AC::solve(s);
    return 0;
}


#include<bits/stdc++.h>
using namespace std;
#define IOS std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
#define debug(...) " [" << #__VA_ARGS__ ": " << (__VA_ARGS__) << "] "
#define IN freopen("in.txt", "r", stdin);
#define endl '\n'
#define pb push_back
#define eb emplace_back
#define fi first
#define se second
#define all(x) x.begin(),x.end()
#define sz(x) (int)x.size()
typedef long long ll;
typedef vector<int> VI;
typedef pair<int, int> pii;
const int inf = 0x3f3f3f3f;

const int N = 4e5+10;
char s[N];
ll dp[1100][1100];
VI v[20];

namespace AC{
    int tr[N][26],tot,val[N],pointval[N],add[1100];
    int e[N],fail[N],to[N];
    void init(){
        memset(tr[0],0,sizeof tr[0]);
        e[0]=0;
        tot=0;
    }
    void insert(char *s,int v){
        int u=0,len=strlen(s);
        for(int i=0;i<len;i++){
            int c=s[i]-'a';
            if(!tr[u][c]){
                memset(tr[++tot],0,sizeof tr[tot]);
                e[tot]=0;
                tr[u][c]=tot;
            }
            u=tr[u][c];
        }
        e[u]=1;
        val[u]+=v;
    }
    void build(){
        queue<int> q;
        fail[0]=0;
        for(int i=0;i<26;i++)
            if(tr[0][i]){
                fail[tr[0][i]]=0;
                q.push(tr[0][i]);
            }
        while(!q.empty()){
            int u=q.front();q.pop();
            for(int i=0;i<26;i++){
                if(tr[u][i])
                    fail[tr[u][i]]=tr[fail[u]][i],
                    q.push(tr[u][i]);
                else
                    tr[u][i]=tr[fail[u]][i];
            }
        }
    }
    void Pre(int u){
        for(int i=u;i;i=fail[i])pointval[u]+=val[i];
    }
    int solve(char *s){
        int len=strlen(s),c=0;
        for(int i=1;i<=tot;i++)Pre(i);
        for(int i=0;i<=tot;i++)fill(dp[i],dp[i]+(1<<15),-inf);
        dp[0][0]=0;
        for(int i=0;i<=tot;i++)to[i]=i;
        for(int po=0;po<len;po++){
            if(s[po]=='?'){
                c++;
                for(int i=0;i<=tot;i++)
                    for(auto it:v[c-1]){
                        if(dp[i][it]==-inf)continue;
                        for(int j=0;j<14;j++){
                            if(it&(1<<j))continue;
                            dp[tr[to[i]][j]][it|(1<<j)]=max(dp[tr[to[i]][j]][it|(1<<j)],dp[i][it]+pointval[tr[to[i]][j]]+add[i]);
                        }
                    }
                for(int i=0;i<=tot;i++)to[i]=i;
                memset(add,0,sizeof add);
            }else{
                int c=s[po]-'a';
                for(int i=0;i<=tot;i++){
                    to[i]=tr[to[i]][c];
                    add[i]+=pointval[to[i]];
                }
            }
        }
        ll res=-inf;
        for(int i=0;i<=tot;i++)for(auto j:v[c])res=max(res,dp[i][j]+add[i]);
        return res;
    }
}

int main(){
    IOS;
    int n,val; cin>>n;
    AC::init();
    for(int i=0;i<n;i++)cin>>s>>val,AC::insert(s,val);
    AC::build();
    for(int i=0;i<(1<<14);i++){
        int c=0;
        for(int j=0;j<14;j++)if(i&(1<<j))c++;
        v[c].pb(i);
    }
    cin>>s;
    cout<<AC::solve(s);
    return 0;
}


#include <algorithm>
#include <cassert>
#include <cstring>
#include <iostream>
#include <map>
#include <vector>
using namespace std;

template<typename T> ostream& operator<<(ostream &os, const vector<T> &v) { os << "{"; for (typename vector<T>::const_iterator vi = v.begin(); vi != v.end(); ++vi) { if (vi != v.begin()) os << ", "; os << *vi; } os << "}"; return os; }
template<typename A, typename B> ostream& operator<<(ostream &os, const pair<A, B> &p) { os << '(' << p.first << ", " << p.second << ')'; return os; }

void dbg_out() { cerr << endl; }
template<typename Head, typename... Tail> void dbg_out(Head H, Tail... T) { cerr << ' ' << H; dbg_out(T...); }

#ifdef NEAL
#define dbg(...) cerr << "(" << #__VA_ARGS__ << "):", dbg_out(__VA_ARGS__)
#else
#define dbg(...)
#endif

// This version of aho_corasick uses a bitmask of size ALPHABET, so it must be modified for ALPHABET > 26.
template<char MIN_CHAR = 'a'>
struct aho_corasick {
    struct node {
        // suff = the index of the node of the longest strict suffix of the current node that's also in the tree.
        //   Also see "blue arcs" on Wikipedia: https://en.wikipedia.org/wiki/Aho%E2%80%93Corasick_algorithm
        // dict = the index of the node of the longest strict suffix of the current node that's in the word list.
        //   Also see "green arcs" on Wikipedia.
        // depth = normal trie depth (root is 0). Can be removed to save memory.
        // word_index = the index of the *first* word ending at this node. -1 if none.
        // word_count = the total number of words ending at this node. Used in count_total_matches().
        // first_child = the first child of this node (children are sequential due to BFS order), -1 if none.
        // child_mask = the bitmask of child keys available from this node. If ALPHABET > 26, change the type.
        int suff = -1, dict = -1, depth = 0;
        int word_index = -1, word_count = 0;
        int first_child = -1;
        unsigned child_mask = 0;
        int cost = 0;

        int get_child(char c) const {
            int bit = c - MIN_CHAR;

            if ((child_mask >> bit & 1) == 0)
                return -1;

            assert(first_child >= 0);
            return first_child + __builtin_popcount(child_mask & ((1 << bit) - 1));
        }
    };

    vector<node> nodes;
    int W;
    vector<string> words;
    vector<int> word_location;
    vector<int> word_indices_by_depth;
    vector<int> defer;

    // aho_corasick(const vector<string> &_words = {}) {
    //     if (!_words.empty())
    //         build(_words);
    // }

    // Builds the adj list based on suffix parents. Often we want to perform DP and/or queries on this tree.
    vector<vector<int>> build_suffix_adj() const {
        vector<vector<int>> adj(nodes.size());

        for (int i = 1; i < (int) nodes.size(); i++)
            adj[nodes[i].suff].push_back(i);

        return adj;
    }

    int get_or_add_child(int current, char c) {
        int bit = c - MIN_CHAR;

        if (nodes[current].child_mask >> bit & 1)
            return nodes[current].get_child(c);

        assert(nodes[current].child_mask >> bit == 0);
        int index = nodes.size();
        nodes[current].child_mask |= 1 << bit;

        if (nodes[current].first_child < 0)
            nodes[current].first_child = index;

        nodes.emplace_back();
        nodes.back().depth = nodes[current].depth + 1;
        return index;
    }

    // Returns where in the trie we should end up after starting at `location` and adding char `c`.
    // Runs in worst case O(depth) but amortizes to O(1) in most situations.
    int get_suffix_link(int location, char c) const {
        int child;

        while (location >= 0 && (child = nodes[location].get_child(c)) < 0)
            location = nodes[location].suff;

        return location < 0 ? 0 : child;
    }

    void build(const vector<string> &_words, const map<string, int> &C) {
        nodes = {node()};
        words = _words;
        stable_sort(words.begin(), words.end());
        words.erase(unique(words.begin(), words.end()), words.end());
        W = words.size();
        word_location.assign(W, 0);
        vector<int> remaining(W);
        int n_rem = W;

        for (int i = 0; i < W; i++)
            remaining[i] = i;

        for (int depth = 0; n_rem > 0; depth++) {
            int next_n_rem = 0;

            for (int i = 0; i < n_rem; i++) {
                int word = remaining[i];
                int &location = word_location[word];
                location = get_or_add_child(location, words[word][depth]);

                if (depth == (int) words[word].size() - 1) {
                    if (nodes[location].word_index < 0)
                        nodes[location].word_index = word;

                    nodes[location].word_count++;
                } else {
                    remaining[next_n_rem++] = word;
                }
            }

            n_rem = next_n_rem;
        }

        int max_depth = 0;
        defer.resize(W);

        for (int i = 0; i < W; i++) {
            max_depth = max(max_depth, (int) words[i].size());
            defer[i] = nodes[word_location[i]].word_index;
        }

        // Create a list of word indices in decreasing order of depth, in linear time via counting sort.
        word_indices_by_depth.resize(W);
        vector<int> depth_freq(max_depth + 1, 0);

        for (int i = 0; i < W; i++)
            depth_freq[words[i].size()]++;

        for (int i = max_depth - 1; i >= 0; i--)
            depth_freq[i] += depth_freq[i + 1];

        for (int i = 0; i < W; i++)
            word_indices_by_depth[--depth_freq[words[i].size()]] = i;

        // Solve suffix parents by traversing in order of depth (BFS order).
        for (int i = 0; i < (int) nodes.size(); i++) {
            unsigned child_mask = nodes[i].child_mask;

            while (child_mask != 0) {
                int bit = __builtin_ctz(child_mask);
                char c = bit + MIN_CHAR;
                int index = nodes[i].get_child(c);
                child_mask ^= 1 << bit;

                // Find index's suffix parent by traversing suffix parents of i until one of them has a child c.
                int suffix_parent = get_suffix_link(nodes[i].suff, c);
                nodes[index].suff = suffix_parent;
                nodes[index].word_count += nodes[suffix_parent].word_count;
                nodes[index].dict = nodes[suffix_parent].word_index < 0 ? nodes[suffix_parent].dict : suffix_parent;
            }
        }

        for (int i = 0; i < (int) nodes.size(); i++) {
            if (nodes[i].word_index >= 0)
                nodes[i].cost += C.at(words[nodes[i].word_index]);

            if (nodes[i].dict >= 0)
                nodes[i].cost += nodes[nodes[i].dict].cost;
        }
    }

    // Counts the number of matches of each word in O(text length + num words).
    vector<int> count_matches(const string &text) const {
        vector<int> matches(W, 0);
        int current = 0;

        for (char c : text) {
            current = get_suffix_link(current, c);
            int dict_node = nodes[current].word_index < 0 ? nodes[current].dict : current;

            if (dict_node >= 0)
                matches[nodes[dict_node].word_index]++;
        }

        // Iterate in decreasing order of depth.
        for (int word_index : word_indices_by_depth) {
            int location = word_location[word_index];
            int dict_node = nodes[location].dict;

            if (dict_node >= 0)
                matches[nodes[dict_node].word_index] += matches[word_index];
        }

        for (int i = 0; i < W; i++)
            matches[i] = matches[defer[i]];

        return matches;
    }

    // Counts the number of matches over all words at each ending position in `text` in O(text length).
    vector<int> count_matches_by_position(const string &text) const {
        vector<int> matches(text.size());
        int current = 0;

        for (int i = 0; i < (int) text.size(); i++) {
            current = get_suffix_link(current, text[i]);
            matches[i] = nodes[current].word_count;
        }

        return matches;
    }

    // Counts the total number of matches of all words within `text` in O(text length).
    long long count_total_matches(const string &text) const {
        long long matches = 0;
        int current = 0;

        for (char c : text) {
            current = get_suffix_link(current, c);
            matches += nodes[current].word_count;
        }

        return matches;
    }
};

const long long LL_INF = (long long) 2e18 + 5;
const int N = 14;
const int T_MAX = 1005;

int K;
vector<string> T;
int A;
aho_corasick AC;
vector<int> C;

int L;
string S;
int Q;
vector<int> questions;
vector<int> question_index;

// transition[index][node] = pair of the node we reach right before the next important point (questions, -1, and L)
// and the cost involved in getting there, when starting from node at index.
pair<int, long long> transition[N + 2][T_MAX];

void compute_transition(int index, int node) {
    int original_node = node;
    int start = questions[index];
    int next = questions[index + 1];
    long long cost = 0;
    dbg(node);

    for (int i = start + 1; i < next; i++) {
        node = AC.get_suffix_link(node, S[i]);
        cost += AC.nodes[node].cost;
        dbg(node, cost);
    }

    dbg("finally", node, cost);
    transition[index][original_node] = {node, cost};
}

pair<int, long long> get_transition(int index, int node, int letter) {
    pair<int, long long> trans = transition[index][node];

    if (letter >= 0) {
        trans.first = AC.get_suffix_link(trans.first, 'a' + letter);
        trans.second += AC.nodes[trans.first].cost;
    }

    return trans;
}

long long dp[1 << N][T_MAX];

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

    cin >> K;
    T.resize(K);
    C.resize(K);
    map<string, int> C_map;

    for (int k = 0; k < K; k++) {
        cin >> T[k] >> C[k];
        C_map[T[k]] += C[k];
    }

    AC.build(T, C_map);
    A = AC.nodes.size();
    cin >> S;
    L = S.size();
    question_index.assign(L, -1);
    questions = {-1};

    for (int i = 0; i < L; i++)
        if (S[i] == '?') {
            question_index[i] = Q++;
            questions.push_back(i);
        }

    questions.push_back(L);

    for (int q = 0; q <= Q; q++)
        for (int node = 0; node < A; node++) {
            compute_transition(q, node);
            dbg(q, node, transition[q][node]);
        }

    memset(dp, -63, sizeof(dp));
    dp[0][0] = 0;

    for (int mask = 0; mask < 1 << N; mask++)
        for (int node = 0; node < A; node++)
            if (dp[mask][node] > -LL_INF) {
                int bit_count = __builtin_popcount(mask);

                for (int next = 0; next < N; next++)
                    if ((mask >> next & 1) == 0) {
                        pair<int, long long> trans = get_transition(bit_count, node, next);
                        int next_mask = mask | 1 << next;
                        dp[next_mask][trans.first] = max(dp[next_mask][trans.first], dp[mask][node] + trans.second);
                    }
            }

    long long answer = -LL_INF;

    for (int mask = 0; mask < 1 << N; mask++)
        if (__builtin_popcount(mask) == Q)
            for (int node = 0; node < A; node++)
                answer = max(answer, dp[mask][node] + transition[Q][node].second);

    cout << answer << '\n';
}


#include <algorithm>
#include <cassert>
#include <cstring>
#include <iostream>
#include <map>
#include <vector>
using namespace std;

template<typename T> ostream& operator<<(ostream &os, const vector<T> &v) { os << "{"; for (typename vector<T>::const_iterator vi = v.begin(); vi != v.end(); ++vi) { if (vi != v.begin()) os << ", "; os << *vi; } os << "}"; return os; }
template<typename A, typename B> ostream& operator<<(ostream &os, const pair<A, B> &p) { os << '(' << p.first << ", " << p.second << ')'; return os; }

void dbg_out() { cerr << endl; }
template<typename Head, typename... Tail> void dbg_out(Head H, Tail... T) { cerr << ' ' << H; dbg_out(T...); }

#ifdef NEAL
#define dbg(...) cerr << "(" << #__VA_ARGS__ << "):", dbg_out(__VA_ARGS__)
#else
#define dbg(...)
#endif

// This version of aho_corasick uses more memory and depends on a small alphabet, but it provides O(1) suffix links.
template<char MIN_CHAR = 'a', int ALPHABET = 14>
struct aho_corasick {
    struct node {
        // suff = the index of the node of the longest strict suffix of the current node that's also in the tree.
        //   Also see "blue arcs" on Wikipedia: https://en.wikipedia.org/wiki/Aho%E2%80%93Corasick_algorithm
        // dict = the index of the node of the longest strict suffix of the current node that's in the word list.
        //   Also see "green arcs" on Wikipedia.
        // depth = normal trie depth (root is 0). Can be removed to save memory.
        // word_index = the index of the *first* word ending at this node. -1 if none.
        // word_count = the total number of words ending at this node. Used in count_total_matches().
        // link = the trie connections and/or suffix connections from this node.
        int suff = -1, dict = -1, depth = 0;
        int word_index = -1, word_count = 0;
        int link[ALPHABET];
        int cost = 0;

        node() {
            fill(link, link + ALPHABET, -1);
        }

        int& operator[](char c) {
            return link[c - MIN_CHAR];
        }
    };

    vector<node> nodes;
    int W;
    vector<int> word_location;
    vector<int> word_indices_by_depth;
    vector<int> defer;

    // aho_corasick(const vector<string> &words = {}) {
    //     if (!words.empty())
    //         build(words);
    // }

    // Builds the adj list based on suffix parents. Often we want to perform DP and/or queries on this tree.
    vector<vector<int>> build_suffix_adj() const {
        vector<vector<int>> adj(nodes.size());

        for (int i = 1; i < (int) nodes.size(); i++)
            adj[nodes[i].suff].push_back(i);

        return adj;
    }

    int get_or_add_child(int current, char c) {
        if (nodes[current][c] >= 0)
            return nodes[current][c];

        int index = nodes.size();
        nodes[current][c] = index;
        nodes.emplace_back();
        nodes.back().depth = nodes[current].depth + 1;
        return index;
    }

    int add_word(const string &word, int word_index) {
        assert(!nodes.empty());
        int current = 0;

        for (char c : word)
            current = get_or_add_child(current, c);

        if (nodes[current].word_index < 0)
            nodes[current].word_index = word_index;

        nodes[current].word_count++;
        return current;
    }

    // Returns where in the trie we should end up after starting at `location` and adding char `c`. Runs in O(1).
    int get_suffix_link(int location, char c) const {
        if (location >= 0)
            location = nodes[location].link[c - MIN_CHAR];

        return max(location, 0);
    }

    void build(vector<string> words, const map<string, int> &C) {
        nodes = {node()};
        stable_sort(words.begin(), words.end());
        words.erase(unique(words.begin(), words.end()), words.end());
        W = words.size();
        word_location.resize(W);
        defer.resize(W);
        int max_depth = 0;

        for (int i = 0; i < W; i++) {
            word_location[i] = add_word(words[i], i);
            max_depth = max(max_depth, (int) words[i].size());
            defer[i] = nodes[word_location[i]].word_index;
        }

        // Create a list of word indices in decreasing order of depth, in linear time via counting sort.
        word_indices_by_depth.resize(W);
        vector<int> depth_freq(max_depth + 1, 0);

        for (int i = 0; i < W; i++)
            depth_freq[words[i].size()]++;

        for (int i = max_depth - 1; i >= 0; i--)
            depth_freq[i] += depth_freq[i + 1];

        for (int i = 0; i < W; i++)
            word_indices_by_depth[--depth_freq[words[i].size()]] = i;

        // Solve suffix parents by traversing in order of depth (BFS order).
        vector<int> q = {0};

        for (int i = 0; i < (int) q.size(); i++) {
            int current = q[i];

            for (char c = MIN_CHAR; c < MIN_CHAR + ALPHABET; c++) {
                int &index = nodes[current][c];

                if (index >= 0) {
                    // Find index's suffix parent by traversing suffix parents of current until one of them has a child c.
                    int suffix_parent = get_suffix_link(nodes[current].suff, c);
                    nodes[index].suff = suffix_parent;
                    nodes[index].word_count += nodes[suffix_parent].word_count;
                    nodes[index].dict = nodes[suffix_parent].word_index < 0 ? nodes[suffix_parent].dict : suffix_parent;
                    q.push_back(index);
                } else {
                    index = get_suffix_link(nodes[current].suff, c);
                }
            }
        }

        vector<int> nodes_by_depth(nodes.size());

        for (int i = 0; i < (int) nodes.size(); i++)
            nodes_by_depth[i] = i;

        sort(nodes_by_depth.begin(), nodes_by_depth.end(), [&](int a, int b) {
            return nodes[a].depth < nodes[b].depth;
        });

        for (int i : nodes_by_depth) {
            if (nodes[i].word_index >= 0)
                nodes[i].cost += C.at(words[nodes[i].word_index]);

            if (nodes[i].dict >= 0)
                nodes[i].cost += nodes[nodes[i].dict].cost;
        }
    }

    // Counts the number of matches of each word in O(text length + num words).
    vector<int> count_matches(const string &text) const {
        vector<int> matches(W, 0);
        int current = 0;

        for (char c : text) {
            current = get_suffix_link(current, c);
            int dict_node = nodes[current].word_index < 0 ? nodes[current].dict : current;

            if (dict_node >= 0)
                matches[nodes[dict_node].word_index]++;
        }

        // Iterate in decreasing order of depth.
        for (int word_index : word_indices_by_depth) {
            int location = word_location[word_index];
            int dict_node = nodes[location].dict;

            if (dict_node >= 0)
                matches[nodes[dict_node].word_index] += matches[word_index];
        }

        for (int i = 0; i < W; i++)
            matches[i] = matches[defer[i]];

        return matches;
    }

    // Counts the number of matches over all words at each ending position in `text` in O(text length).
    vector<int> count_matches_by_position(const string &text) const {
        vector<int> matches(text.size());
        int current = 0;

        for (int i = 0; i < (int) text.size(); i++) {
            current = get_suffix_link(current, text[i]);
            matches[i] = nodes[current].word_count;
        }

        return matches;
    }

    // Counts the total number of matches of all words within `text` in O(text length).
    long long count_total_matches(const string &text) const {
        long long matches = 0;
        int current = 0;

        for (char c : text) {
            current = get_suffix_link(current, c);
            matches += nodes[current].word_count;
        }

        return matches;
    }
};

const long long LL_INF = (long long) 2e18 + 5;
const int N = 14;
const int T_MAX = 1005;

int K;
vector<string> T;
int A;
aho_corasick AC;
vector<int> C;

int L;
string S;
int Q;
vector<int> questions;
vector<int> question_index;

// transition[index][node] = pair of the node we reach right before the next important point (questions, -1, and L)
// and the cost involved in getting there, when starting from node at index.
pair<int, long long> transition[N + 2][T_MAX];

void compute_transition(int index, int node) {
    int original_node = node;
    int start = questions[index];
    int next = questions[index + 1];
    long long cost = 0;

    for (int i = start + 1; i < next; i++) {
        node = AC.get_suffix_link(node, S[i]);
        cost += AC.nodes[node].cost;
    }

    transition[index][original_node] = {node, cost};
}

pair<int, long long> get_transition(int index, int node, int letter) {
    pair<int, long long> trans = transition[index][node];

    if (letter >= 0) {
        trans.first = AC.get_suffix_link(trans.first, 'a' + letter);
        trans.second += AC.nodes[trans.first].cost;
    }

    return trans;
}

long long dp[1 << N][T_MAX];

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

    cin >> K;
    T.resize(K);
    C.resize(K);
    map<string, int> C_map;

    for (int k = 0; k < K; k++) {
        cin >> T[k] >> C[k];
        C_map[T[k]] += C[k];
    }

    AC.build(T, C_map);
    A = AC.nodes.size();
    cin >> S;
    L = S.size();
    question_index.assign(L, -1);
    questions = {-1};

    for (int i = 0; i < L; i++)
        if (S[i] == '?') {
            question_index[i] = Q++;
            questions.push_back(i);
        }

    questions.push_back(L);

    for (int q = 0; q <= Q; q++)
        for (int node = 0; node < A; node++)
            compute_transition(q, node);

    memset(dp, -63, sizeof(dp));
    dp[0][0] = 0;

    for (int mask = 0; mask < 1 << N; mask++)
        for (int node = 0; node < A; node++)
            if (dp[mask][node] > -LL_INF) {
                int bit_count = __builtin_popcount(mask);

                for (int next = 0; next < N; next++)
                    if ((mask >> next & 1) == 0) {
                        pair<int, long long> trans = get_transition(bit_count, node, next);
                        int next_mask = mask | 1 << next;
                        dp[next_mask][trans.first] = max(dp[next_mask][trans.first], dp[mask][node] + trans.second);
                    }
            }

    long long answer = -LL_INF;

    for (int mask = 0; mask < 1 << N; mask++)
        if (__builtin_popcount(mask) == Q)
            for (int node = 0; node < A; node++)
                answer = max(answer, dp[mask][node] + transition[Q][node].second);

    cout << answer << '\n';
}


#include <algorithm>
#include <cassert>
#include <cstring>
#include <iostream>
#include <map>
#include <vector>
using namespace std;

template<typename T> ostream& operator<<(ostream &os, const vector<T> &v) { os << "{"; for (typename vector<T>::const_iterator vi = v.begin(); vi != v.end(); ++vi) { if (vi != v.begin()) os << ", "; os << *vi; } os << "}"; return os; }
template<typename A, typename B> ostream& operator<<(ostream &os, const pair<A, B> &p) { os << '(' << p.first << ", " << p.second << ')'; return os; }

void dbg_out() { cerr << endl; }
template<typename Head, typename... Tail> void dbg_out(Head H, Tail... T) { cerr << ' ' << H; dbg_out(T...); }

#ifdef NEAL
#define dbg(...) cerr << "(" << #__VA_ARGS__ << "):", dbg_out(__VA_ARGS__)
#else
#define dbg(...)
#endif

// This version of aho_corasick uses more memory and depends on a small alphabet, but it provides O(1) suffix links.
template<char MIN_CHAR = 'a', int ALPHABET = 14>
struct aho_corasick {
    struct node {
        // suff = the index of the node of the longest strict suffix of the current node that's also in the tree.
        //   Also see "blue arcs" on Wikipedia: https://en.wikipedia.org/wiki/Aho%E2%80%93Corasick_algorithm
        // dict = the index of the node of the longest strict suffix of the current node that's in the word list.
        //   Also see "green arcs" on Wikipedia.
        // depth = normal trie depth (root is 0). Can be removed to save memory.
        // word_index = the index of the *first* word ending at this node. -1 if none.
        // word_count = the total number of words ending at this node. Used in count_total_matches().
        // link = the trie connections and/or suffix connections from this node.
        int suff = -1, dict = -1, depth = 0;
        int word_index = -1, word_count = 0;
        int link[ALPHABET];
        int cost = 0;

        node() {
            fill(link, link + ALPHABET, -1);
        }

        int& operator[](char c) {
            return link[c - MIN_CHAR];
        }
    };

    vector<node> nodes;
    int W;
    vector<int> word_location;
    vector<int> word_indices_by_depth;
    vector<int> defer;

    // aho_corasick(const vector<string> &words = {}) {
    //     if (!words.empty())
    //         build(words);
    // }

    // Builds the adj list based on suffix parents. Often we want to perform DP and/or queries on this tree.
    vector<vector<int>> build_suffix_adj() const {
        vector<vector<int>> adj(nodes.size());

        for (int i = 1; i < (int) nodes.size(); i++)
            adj[nodes[i].suff].push_back(i);

        return adj;
    }

    int get_or_add_child(int current, char c) {
        if (nodes[current][c] >= 0)
            return nodes[current][c];

        int index = nodes.size();
        nodes[current][c] = index;
        nodes.emplace_back();
        nodes.back().depth = nodes[current].depth + 1;
        return index;
    }

    int add_word(const string &word, int word_index) {
        assert(!nodes.empty());
        int current = 0;

        for (char c : word)
            current = get_or_add_child(current, c);

        if (nodes[current].word_index < 0)
            nodes[current].word_index = word_index;

        nodes[current].word_count++;
        return current;
    }

    // Returns where in the trie we should end up after starting at `location` and adding char `c`. Runs in O(1).
    int get_suffix_link(int location, char c) const {
        if (location >= 0)
            location = nodes[location].link[c - MIN_CHAR];

        return max(location, 0);
    }

    void build(vector<string> words, const map<string, int> &C) {
        nodes = {node()};
        stable_sort(words.begin(), words.end());
        words.erase(unique(words.begin(), words.end()), words.end());
        W = words.size();
        word_location.resize(W);
        defer.resize(W);
        int max_depth = 0;

        for (int i = 0; i < W; i++) {
            word_location[i] = add_word(words[i], i);
            max_depth = max(max_depth, (int) words[i].size());
            defer[i] = nodes[word_location[i]].word_index;
        }

        // Create a list of word indices in decreasing order of depth, in linear time via counting sort.
        word_indices_by_depth.resize(W);
        vector<int> depth_freq(max_depth + 1, 0);

        for (int i = 0; i < W; i++)
            depth_freq[words[i].size()]++;

        for (int i = max_depth - 1; i >= 0; i--)
            depth_freq[i] += depth_freq[i + 1];

        for (int i = 0; i < W; i++)
            word_indices_by_depth[--depth_freq[words[i].size()]] = i;

        // Solve suffix parents by traversing in order of depth (BFS order).
        vector<int> q = {0};

        for (int i = 0; i < (int) q.size(); i++) {
            int current = q[i];

            for (char c = MIN_CHAR; c < MIN_CHAR + ALPHABET; c++) {
                int &index = nodes[current][c];

                if (index >= 0) {
                    // Find index's suffix parent by traversing suffix parents of current until one of them has a child c.
                    int suffix_parent = get_suffix_link(nodes[current].suff, c);
                    nodes[index].suff = suffix_parent;
                    nodes[index].word_count += nodes[suffix_parent].word_count;
                    nodes[index].dict = nodes[suffix_parent].word_index < 0 ? nodes[suffix_parent].dict : suffix_parent;
                    q.push_back(index);
                } else {
                    index = get_suffix_link(nodes[current].suff, c);
                }
            }
        }

        vector<int> nodes_by_depth(nodes.size());

        for (int i = 0; i < (int) nodes.size(); i++)
            nodes_by_depth[i] = i;

        sort(nodes_by_depth.begin(), nodes_by_depth.end(), [&](int a, int b) {
            return nodes[a].depth < nodes[b].depth;
        });

        for (int i : nodes_by_depth) {
            if (nodes[i].word_index >= 0)
                nodes[i].cost += C.at(words[nodes[i].word_index]);

            if (nodes[i].dict >= 0)
                nodes[i].cost += nodes[nodes[i].dict].cost;
        }
    }

    // Counts the number of matches of each word in O(text length + num words).
    vector<int> count_matches(const string &text) const {
        vector<int> matches(W, 0);
        int current = 0;

        for (char c : text) {
            current = get_suffix_link(current, c);
            int dict_node = nodes[current].word_index < 0 ? nodes[current].dict : current;

            if (dict_node >= 0)
                matches[nodes[dict_node].word_index]++;
        }

        // Iterate in decreasing order of depth.
        for (int word_index : word_indices_by_depth) {
            int location = word_location[word_index];
            int dict_node = nodes[location].dict;

            if (dict_node >= 0)
                matches[nodes[dict_node].word_index] += matches[word_index];
        }

        for (int i = 0; i < W; i++)
            matches[i] = matches[defer[i]];

        return matches;
    }

    // Counts the number of matches over all words at each ending position in `text` in O(text length).
    vector<int> count_matches_by_position(const string &text) const {
        vector<int> matches(text.size());
        int current = 0;

        for (int i = 0; i < (int) text.size(); i++) {
            current = get_suffix_link(current, text[i]);
            matches[i] = nodes[current].word_count;
        }

        return matches;
    }

    // Counts the total number of matches of all words within `text` in O(text length).
    long long count_total_matches(const string &text) const {
        long long matches = 0;
        int current = 0;

        for (char c : text) {
            current = get_suffix_link(current, c);
            matches += nodes[current].word_count;
        }

        return matches;
    }
};

const long long LL_INF = (long long) 2e18 + 5;
const int N = 14;
const int T_MAX = 1005;

int K;
vector<string> T;
int A;
aho_corasick AC;
vector<int> C;

int L;
string S;
int Q;
vector<int> questions;
vector<int> question_index;

// transition[index][node] = pair of the node we reach right before the next important point (questions, -1, and L)
// and the cost involved in getting there, when starting from node at index.
pair<int, long long> transition[N + 2][T_MAX];
pair<int, long long> fast_forward[N + 2];

void compute_transition(int index, int node) {
    int original_node = node;
    int start = questions[index];
    int next = questions[index + 1];
    long long cost = 0;

    for (int i = start + 1; i < next; i++) {
        node = AC.get_suffix_link(node, S[i]);
        cost += AC.nodes[node].cost;

        if (i == start + T_MAX) {
            if (fast_forward[index].first >= 0) {
                node = fast_forward[index].first;
                cost += fast_forward[index].second;
                transition[index][original_node] = {node, cost};
                return;
            }

            fast_forward[index].second = -cost;
        }
    }

    fast_forward[index].first = node;
    fast_forward[index].second += cost;
    transition[index][original_node] = {node, cost};
}

pair<int, long long> get_transition(int index, int node, int letter) {
    if (transition[index][node].first < 0)
        compute_transition(index, node);

    pair<int, long long> trans = transition[index][node];

    if (letter >= 0) {
        trans.first = AC.get_suffix_link(trans.first, 'a' + letter);
        trans.second += AC.nodes[trans.first].cost;
    }

    return trans;
}

long long dp[1 << N][T_MAX];

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

    cin >> K;
    T.resize(K);
    C.resize(K);
    map<string, int> C_map;

    for (int k = 0; k < K; k++) {
        cin >> T[k] >> C[k];
        C_map[T[k]] += C[k];
    }

    AC.build(T, C_map);
    A = AC.nodes.size();
    cin >> S;
    L = S.size();
    question_index.assign(L, -1);
    questions = {-1};

    for (int i = 0; i < L; i++)
        if (S[i] == '?') {
            question_index[i] = Q++;
            questions.push_back(i);
        }

    questions.push_back(L);
    memset(transition, -63, sizeof(transition));
    memset(fast_forward, -63, sizeof(fast_forward));
    memset(dp, -63, sizeof(dp));
    dp[0][0] = 0;

    for (int mask = 0; mask < 1 << N; mask++)
        for (int node = 0; node < A; node++)
            if (__builtin_popcount(mask) < Q && dp[mask][node] > -LL_INF) {
                int bit_count = __builtin_popcount(mask);

                for (int next = 0; next < N; next++)
                    if ((mask >> next & 1) == 0) {
                        pair<int, long long> trans = get_transition(bit_count, node, next);
                        int next_mask = mask | 1 << next;
                        dp[next_mask][trans.first] = max(dp[next_mask][trans.first], dp[mask][node] + trans.second);
                    }
            }

    long long answer = -LL_INF;

    for (int mask = 0; mask < 1 << N; mask++)
        if (__builtin_popcount(mask) == Q)
            for (int node = 0; node < A; node++)
                answer = max(answer, dp[mask][node] + get_transition(Q, node, -1).second);

    cout << answer << '\n';
}


#include <algorithm>
#include <cassert>
#include <cstring>
#include <iostream>
#include <map>
#include <vector>
using namespace std;

template<typename T> ostream& operator<<(ostream &os, const vector<T> &v) { os << "{"; for (typename vector<T>::const_iterator vi = v.begin(); vi != v.end(); ++vi) { if (vi != v.begin()) os << ", "; os << *vi; } os << "}"; return os; }
template<typename A, typename B> ostream& operator<<(ostream &os, const pair<A, B> &p) { os << '(' << p.first << ", " << p.second << ')'; return os; }

void dbg_out() { cerr << endl; }
template<typename Head, typename... Tail> void dbg_out(Head H, Tail... T) { cerr << ' ' << H; dbg_out(T...); }

#ifdef NEAL
#define dbg(...) cerr << "(" << #__VA_ARGS__ << "):", dbg_out(__VA_ARGS__)
#else
#define dbg(...)
#endif

// This version of aho_corasick uses more memory and depends on a small alphabet, but it provides O(1) suffix links.
template<char MIN_CHAR = 'a', int ALPHABET = 14>
struct aho_corasick {
    struct node {
        // suff = the index of the node of the longest strict suffix of the current node that's also in the tree.
        //   Also see "blue arcs" on Wikipedia: https://en.wikipedia.org/wiki/Aho%E2%80%93Corasick_algorithm
        // dict = the index of the node of the longest strict suffix of the current node that's in the word list.
        //   Also see "green arcs" on Wikipedia.
        // depth = normal trie depth (root is 0). Can be removed to save memory.
        // word_index = the index of the *first* word ending at this node. -1 if none.
        // word_count = the total number of words ending at this node. Used in count_total_matches().
        // link = the trie connections and/or suffix connections from this node.
        int suff = -1, dict = -1, depth = 0;
        int word_index = -1, word_count = 0;
        int link[ALPHABET];
        int cost = 0;

        node() {
            fill(link, link + ALPHABET, -1);
        }

        int& operator[](char c) {
            return link[c - MIN_CHAR];
        }
    };

    vector<node> nodes;
    int W;
    vector<int> word_location;
    vector<int> word_indices_by_depth;
    vector<int> defer;

    // aho_corasick(const vector<string> &words = {}) {
    //     if (!words.empty())
    //         build(words);
    // }

    // Builds the adj list based on suffix parents. Often we want to perform DP and/or queries on this tree.
    vector<vector<int>> build_suffix_adj() const {
        vector<vector<int>> adj(nodes.size());

        for (int i = 1; i < (int) nodes.size(); i++)
            adj[nodes[i].suff].push_back(i);

        return adj;
    }

    int get_or_add_child(int current, char c) {
        if (nodes[current][c] >= 0)
            return nodes[current][c];

        int index = nodes.size();
        nodes[current][c] = index;
        nodes.emplace_back();
        nodes.back().depth = nodes[current].depth + 1;
        return index;
    }

    int add_word(const string &word, int word_index) {
        assert(!nodes.empty());
        int current = 0;

        for (char c : word)
            current = get_or_add_child(current, c);

        if (nodes[current].word_index < 0)
            nodes[current].word_index = word_index;

        nodes[current].word_count++;
        return current;
    }

    // Returns where in the trie we should end up after starting at `location` and adding char `c`. Runs in O(1).
    int get_suffix_link(int location, char c) const {
        if (location >= 0)
            location = nodes[location].link[c - MIN_CHAR];

        return max(location, 0);
    }

    void build(vector<string> words, const map<string, int> &C) {
        nodes = {node()};
        stable_sort(words.begin(), words.end());
        words.erase(unique(words.begin(), words.end()), words.end());
        W = words.size();
        word_location.resize(W);
        defer.resize(W);
        int max_depth = 0;

        for (int i = 0; i < W; i++) {
            word_location[i] = add_word(words[i], i);
            max_depth = max(max_depth, (int) words[i].size());
            defer[i] = nodes[word_location[i]].word_index;
        }

        // Create a list of word indices in decreasing order of depth, in linear time via counting sort.
        word_indices_by_depth.resize(W);
        vector<int> depth_freq(max_depth + 1, 0);

        for (int i = 0; i < W; i++)
            depth_freq[words[i].size()]++;

        for (int i = max_depth - 1; i >= 0; i--)
            depth_freq[i] += depth_freq[i + 1];

        for (int i = 0; i < W; i++)
            word_indices_by_depth[--depth_freq[words[i].size()]] = i;

        // Solve suffix parents by traversing in order of depth (BFS order).
        vector<int> q = {0};

        for (int i = 0; i < (int) q.size(); i++) {
            int current = q[i];

            for (char c = MIN_CHAR; c < MIN_CHAR + ALPHABET; c++) {
                int &index = nodes[current][c];

                if (index >= 0) {
                    // Find index's suffix parent by traversing suffix parents of current until one of them has a child c.
                    int suffix_parent = get_suffix_link(nodes[current].suff, c);
                    nodes[index].suff = suffix_parent;
                    nodes[index].word_count += nodes[suffix_parent].word_count;
                    nodes[index].dict = nodes[suffix_parent].word_index < 0 ? nodes[suffix_parent].dict : suffix_parent;
                    q.push_back(index);
                } else {
                    index = get_suffix_link(nodes[current].suff, c);
                }
            }
        }

        vector<int> nodes_by_depth(nodes.size());

        for (int i = 0; i < (int) nodes.size(); i++)
            nodes_by_depth[i] = i;

        sort(nodes_by_depth.begin(), nodes_by_depth.end(), [&](int a, int b) {
            return nodes[a].depth < nodes[b].depth;
        });

        for (int i : nodes_by_depth) {
            if (nodes[i].word_index >= 0)
                nodes[i].cost += C.at(words[nodes[i].word_index]);

            if (nodes[i].dict >= 0)
                nodes[i].cost += nodes[nodes[i].dict].cost;
        }
    }

    // Counts the number of matches of each word in O(text length + num words).
    vector<int> count_matches(const string &text) const {
        vector<int> matches(W, 0);
        int current = 0;

        for (char c : text) {
            current = get_suffix_link(current, c);
            int dict_node = nodes[current].word_index < 0 ? nodes[current].dict : current;

            if (dict_node >= 0)
                matches[nodes[dict_node].word_index]++;
        }

        // Iterate in decreasing order of depth.
        for (int word_index : word_indices_by_depth) {
            int location = word_location[word_index];
            int dict_node = nodes[location].dict;

            if (dict_node >= 0)
                matches[nodes[dict_node].word_index] += matches[word_index];
        }

        for (int i = 0; i < W; i++)
            matches[i] = matches[defer[i]];

        return matches;
    }

    // Counts the number of matches over all words at each ending position in `text` in O(text length).
    vector<int> count_matches_by_position(const string &text) const {
        vector<int> matches(text.size());
        int current = 0;

        for (int i = 0; i < (int) text.size(); i++) {
            current = get_suffix_link(current, text[i]);
            matches[i] = nodes[current].word_count;
        }

        return matches;
    }

    // Counts the total number of matches of all words within `text` in O(text length).
    long long count_total_matches(const string &text) const {
        long long matches = 0;
        int current = 0;

        for (char c : text) {
            current = get_suffix_link(current, c);
            matches += nodes[current].word_count;
        }

        return matches;
    }
};

const long long LL_INF = (long long) 2e18 + 5;
const int N = 14;
const int T_MAX = 1005;

int K;
vector<string> T;
int A;
aho_corasick AC;
vector<int> C;

int L;
string S;
int Q;
vector<int> questions;
vector<int> question_index;

// transition[index][node] = pair of the node we reach right before the next important point (questions, -1, and L)
// and the cost involved in getting there, when starting from node at index.
pair<int, long long> transition[N + 2][T_MAX];

void compute_transition(int index, int node) {
    int original_node = node;
    int start = questions[index];
    int next = questions[index + 1];
    long long cost = 0;

    for (int i = start + 1; i < next; i++) {
        node = AC.get_suffix_link(node, S[i]);
        cost += AC.nodes[node].cost;
    }

    transition[index][original_node] = {node, cost};
}

pair<int, long long> get_transition(int index, int node, int letter) {
    if (transition[index][node].first < 0)
        compute_transition(index, node);

    pair<int, long long> trans = transition[index][node];

    if (letter >= 0) {
        trans.first = AC.get_suffix_link(trans.first, 'a' + letter);
        trans.second += AC.nodes[trans.first].cost;
    }

    return trans;
}

long long dp[1 << N][T_MAX];

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

    cin >> K;
    T.resize(K);
    C.resize(K);
    map<string, int> C_map;

    for (int k = 0; k < K; k++) {
        cin >> T[k] >> C[k];
        C_map[T[k]] += C[k];
    }

    AC.build(T, C_map);
    A = AC.nodes.size();
    cin >> S;
    L = S.size();
    question_index.assign(L, -1);
    questions = {-1};

    for (int i = 0; i < L; i++)
        if (S[i] == '?') {
            question_index[i] = Q++;
            questions.push_back(i);
        }

    questions.push_back(L);
    memset(transition, -63, sizeof(transition));
    memset(dp, -63, sizeof(dp));
    dp[0][0] = 0;

    for (int mask = 0; mask < 1 << N; mask++)
        for (int node = 0; node < A; node++)
            if (__builtin_popcount(mask) < Q && dp[mask][node] > -LL_INF) {
                int bit_count = __builtin_popcount(mask);

                for (int next = 0; next < N; next++)
                    if ((mask >> next & 1) == 0) {
                        pair<int, long long> trans = get_transition(bit_count, node, next);
                        int next_mask = mask | 1 << next;
                        dp[next_mask][trans.first] = max(dp[next_mask][trans.first], dp[mask][node] + trans.second);
                    }
            }

    long long answer = -LL_INF;

    for (int mask = 0; mask < 1 << N; mask++)
        if (__builtin_popcount(mask) == Q)
            for (int node = 0; node < A; node++)
                answer = max(answer, dp[mask][node] + get_transition(Q, node, -1).second);

    cout << answer << '\n';
}


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

typedef long long ll;
typedef long double ld;
typedef pair<int, int> ii;
typedef vector<int> vi;
typedef vector<ii> vii;
typedef vector<vi> vvi;
typedef vector<vii> vvii;
#define fi first
#define se second
#define ALL(x) (x).begin(), (x).end()
#define RALL(x) (x).rbegin(), (x).rend()
#define pf push_front
#define eb emplace_back
#define pb push_back
#define mp make_pair
#define mt make_tuple
#define ge(a, b) get<(b)>((a))
#define SZ(x) (x).size()
#define endl '\n'
#define FOR(a, b, c) for (auto(a) = (b); (a) < (c); ++(a))
#define ROF(a, b, c) for (auto(a) = (b); (a) > (c); --(a))
#define F0R(a, b) FOR ((a), 0, (b))
#define R0F(a, b) ROF ((a), (b), -1)
#define CEIL(a, b) ((a) + (b)-1) / (b)
#define CLR(a, b) memset((a), (b), sizeof((a)))
template <typename t> bool ckmin(t& a, const t& b) {return a > b ? a = b, true : false;}
template <typename t> bool ckmax(t& a, const t& b) {return a < b ? a = b, true : false;}

const int maxn = 1e3 + 7;
const ll inf = 3e18;
const int mod = 1e9 + 7;

int n;
string s;
int nxt[maxn][26], fail[maxn], cnt, to[maxn];
ll sum[maxn], dp[maxn][1 << 14], tmp[maxn];

void insert(int cc) {
    int curr = 0;
    for (char c : s) {
        if (!nxt[curr][c - 'a'])
            nxt[curr][c - 'a'] = ++cnt;
        curr = nxt[curr][c - 'a'];
    }
    sum[curr] += cc;
}

void build() {
    queue<int> q;
    F0R(i, 14)
        if (nxt[0][i])
            q.push(nxt[0][i]);
    while (!q.empty()) {
        int curr = q.front();
        q.pop();
        F0R(i, 14) {
            if (nxt[curr][i]) {
                fail[nxt[curr][i]] = nxt[fail[curr]][i];
                sum[nxt[curr][i]] += sum[nxt[fail[curr]][i]];
                q.push(nxt[curr][i]);
            } else
                nxt[curr][i] = nxt[fail[curr]][i];
        }
    }
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);

    cin >> n;
    F0R(i, n) {
        cin >> s;
        int c;
        cin >> c;
        insert(c);
    }
    build();
    cin >> s;

    F0R(i, cnt + 1)
        F0R(j, (1 << 14))
            dp[i][j] = -inf;

    dp[0][0] = 0;
    F0R(p, cnt + 1) {
        tmp[p] = 0;
        to[p] = p;
    }

    for (char c : s) {
        if (c == '?') {
            for (int s = (1 << 14) - 1; s >= 0; --s) {
                F0R(p, cnt + 1) {
                    if(dp[p][s] == -inf)
                        continue;
                    F0R(i, 14) {
                        if(s >> i & 1)
                            continue;
                        ckmax(dp[nxt[to[p]][i]][s | 1 << i],
                              dp[p][s] + tmp[p] + sum[nxt[to[p]][i]]);
                    }
                    dp[p][s] = -inf;
                }
            }
            F0R(p, cnt + 1) {
                tmp[p] = 0;
                to[p] = p;
            }
        } else {
            F0R(p, cnt + 1) {
                to[p] = nxt[to[p]][c - 'a'];
                tmp[p] += sum[to[p]];
            }
        }
    }

    ll ans = -inf;
    F0R(p, cnt + 1)
        F0R(s, (1 << 14))
            ckmax(ans, dp[p][s] + tmp[p]);
    cout << ans << endl;
}


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

typedef long long ll;
typedef long double ld;
typedef pair<int, int> ii;
typedef vector<int> vi;
typedef vector<ii> vii;
typedef vector<vi> vvi;
typedef vector<vii> vvii;
#define fi first
#define se second
#define ALL(x) (x).begin(), (x).end()
#define RALL(x) (x).rbegin(), (x).rend()
#define pf push_front
#define eb emplace_back
#define pb push_back
#define mp make_pair
#define mt make_tuple
#define ge(a, b) get<(b)>((a))
#define SZ(x) (x).size()
#define endl '\n'
#define FOR(a, b, c) for (auto(a) = (b); (a) < (c); ++(a))
#define ROF(a, b, c) for (auto(a) = (b); (a) > (c); --(a))
#define F0R(a, b) FOR ((a), 0, (b))
#define R0F(a, b) ROF ((a), (b), -1)
#define CEIL(a, b) ((a) + (b)-1) / (b)
#define CLR(a, b) memset((a), (b), sizeof((a)))
template <typename t> bool ckmin(t& a, const t& b) {return a > b ? a = b, true : false;}
template <typename t> bool ckmax(t& a, const t& b) {return a < b ? a = b, true : false;}

const int MAXN = 1e3 + 7,
          MAXK = 14;
const ll NINF = -3e18;

int nxt[MAXN][26], fail[MAXN], nodeCnt, to[MAXN];
ll sum[MAXN], dp[MAXN][1 << MAXK];

void insert(string& s, int cost) {
    int curr = 0;
    for (char c : s) {
        if (!nxt[curr][c - 'a'])
            nxt[curr][c - 'a'] = ++nodeCnt;
        curr = nxt[curr][c - 'a'];
    }
    sum[curr] += cost;
}

void build() {
    queue<int> q;
    F0R(i, MAXK)
        if (nxt[0][i])
            q.push(nxt[0][i]);

    while (!q.empty()) {
        int curr = q.front();
        q.pop();
        F0R(i, MAXK) {
            if (nxt[curr][i]) {
                fail[nxt[curr][i]] = nxt[fail[curr]][i];
                sum[nxt[curr][i]] += sum[nxt[fail[curr]][i]];
                q.push(nxt[curr][i]);
            } else
                nxt[curr][i] = nxt[fail[curr]][i];
        }
    }
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);

    int n, cost;
    string s;
    cin >> n;
    F0R(i, n) {
        cin >> s >> cost;
        insert(s, cost);
    }
    build();
    cin >> s;

    F0R(i, nodeCnt + 1)
        F0R(j, nodeCnt + 1)
            dp[i][j] = NINF;
    dp[0][0] = 0;

    vi tmp(nodeCnt + 1, 0);
    vi to(nodeCnt + 1);
    iota(ALL(to), 0);

    for (char c : s) {
        if (c == '?') {
            R0F(s, (1 << MAXK) - 1) {
                F0R(p, nodeCnt + 1) {
                    if(dp[p][s] == NINF)
                        continue;
                    F0R(i, MAXK) {
                        if(s >> i & 1)
                            continue;
                        ckmax(dp[nxt[to[p]][i]][s | 1 << i],
                              dp[p][s] + tmp[p] + sum[nxt[to[p]][i]]);
                    }
                    dp[p][s] = NINF;
                }
            }
            F0R(p, nodeCnt + 1) {
                tmp[p] = 0;
                to[p] = p;
            }
        } else {
            F0R(p, nodeCnt + 1) {
                to[p] = nxt[to[p]][c - 'a'];
                tmp[p] += sum[to[p]];
            }
        }
    }

    ll ans = NINF;
    F0R(p, nodeCnt + 1)
        F0R(s, 1 << MAXK)
            ckmax(ans, dp[p][s] + tmp[p]);
    cout << ans << endl;
}


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

typedef long long ll;
typedef long double ld;
typedef pair<int, int> ii;
typedef vector<int> vi;
typedef vector<ii> vii;
typedef vector<vi> vvi;
typedef vector<vii> vvii;
#define fi first
#define se second
#define ALL(x) (x).begin(), (x).end()
#define RALL(x) (x).rbegin(), (x).rend()
#define pf push_front
#define eb emplace_back
#define pb push_back
#define mp make_pair
#define mt make_tuple
#define ge(a, b) get<(b)>((a))
#define SZ(x) (x).size()
#define endl '\n'
#define FOR(a, b, c) for (auto(a) = (b); (a) < (c); ++(a))
#define ROF(a, b, c) for (auto(a) = (b); (a) > (c); --(a))
#define F0R(a, b) FOR ((a), 0, (b))
#define R0F(a, b) ROF ((a), (b), -1)
#define CEIL(a, b) ((a) + (b)-1) / (b)
#define CLR(a, b) memset((a), (b), sizeof((a)))
template <typename t> bool ckmin(t& a, const t& b) { return a > b ? a = b, true : false; }
template <typename t> bool ckmax(t& a, const t& b) { return a < b ? a = b, true : false; }

const int MAXN = 1e3 + 7,
          MAXK = 14;

int nxt[MAXN][26], fail[MAXN], nodeCnt;
ll sum[MAXN], dp[MAXN][1 << MAXK];

void insert(string& s, int cost) {
    int curr = 0;
    for(char c : s) {
        if(!nxt[curr][c - 'a'])
            nxt[curr][c - 'a'] = ++nodeCnt;
        curr = nxt[curr][c - 'a'];
    }
    sum[curr] += cost;
}

void build() {
    queue<int> q;
    F0R(i, MAXK)
        if(nxt[0][i])
            q.push(nxt[0][i]);

    while(!q.empty()) {
        int curr = q.front();
        q.pop();
        F0R(i, MAXK) {
            if(nxt[curr][i]) {
                fail[nxt[curr][i]] = nxt[fail[curr]][i];
                sum[nxt[curr][i]] += sum[nxt[fail[curr]][i]];
                q.push(nxt[curr][i]);
            } else {
                nxt[curr][i] = nxt[fail[curr]][i];
            }
        }
    }
}

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

    int n, cost;
    cin >> n;

    string s;
    F0R(i, n) {
        cin >> s >> cost;
        insert(s, cost);
    }
    cin >> s;

    build();

    CLR(dp, -1e9);
    vi tmp(nodeCnt + 1, 0);
    vi to(nodeCnt + 1);
    iota(ALL(to), 0);

    dp[0][0] = 0;
    for(char c : s) {
        if(c == '?') {
            R0F(s, (1 << MAXK) - 1) {
                F0R(p, nodeCnt + 1) {
                    if(dp[p][s] == -1e9)
                        continue;
                    F0R(i, MAXK) {
                        if(s & (1 << i))
                            continue;
                        ckmax(dp[nxt[to[p]][i]][s|1<<i],
                              dp[p][s] + tmp[p] + sum[nxt[to[p]][i]]);
                    }
                    dp[p][s] = -1e9;
                }
            }
            fill(ALL(tmp), 0);
            iota(ALL(to), 0);
        } else {
            F0R(p, nodeCnt + 1) {
                to[p] = nxt[to[p]][c - 'a'];
                tmp[p] += sum[to[p]];
            }
        }
    }
    ll res = -1e9;
    F0R(p, nodeCnt + 1)
        F0R(s, 1 << MAXK)
            ckmax(res, dp[p][s] + tmp[p]);
    cout << res << endl;
}


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

typedef long long ll;
typedef long double ld;
typedef pair<int, int> ii;
typedef vector<int> vi;
typedef vector<ii> vii;
typedef vector<vi> vvi;
typedef vector<vii> vvii;
#define fi first
#define se second
#define ALL(x) (x).begin(), (x).end()
#define RALL(x) (x).rbegin(), (x).rend()
#define pf push_front
#define eb emplace_back
#define pb push_back
#define mp make_pair
#define mt make_tuple
#define ge(a, b) get<(b)>((a))
#define SZ(x) (x).size()
#define endl '\n'
#define FOR(a, b, c) for (auto(a) = (b); (a) < (c); ++(a))
#define ROF(a, b, c) for (auto(a) = (b); (a) > (c); --(a))
#define F0R(a, b) FOR ((a), 0, (b))
#define R0F(a, b) ROF ((a), (b), -1)
#define CEIL(a, b) ((a) + (b)-1) / (b)
#define CLR(a, b) memset((a), (b), sizeof((a)))
template <typename t> bool ckmin(t& a, const t& b) {return a > b ? a = b, true : false;}
template <typename t> bool ckmax(t& a, const t& b) {return a < b ? a = b, true : false;}

const int MAXN = 1e3 + 7;
const ll INF = 3e18;

int n;
string s;
int nxt[MAXN][26], fail[MAXN], cnt, to[MAXN];
ll sum[MAXN], dp[MAXN][1 << 14], tmp[MAXN];

void insert(int cc) {
    int curr = 0;
    for (char c : s) {
        if (!nxt[curr][c - 'a'])
            nxt[curr][c - 'a'] = ++cnt;
        curr = nxt[curr][c - 'a'];
    }
    sum[curr] += cc;
}

void build() {
    queue<int> q;
    F0R(i, 14)
        if (nxt[0][i])
            q.push(nxt[0][i]);
    while (!q.empty()) {
        int curr = q.front();
        q.pop();
        F0R(i, 14) {
            if (nxt[curr][i]) {
                fail[nxt[curr][i]] = nxt[fail[curr]][i];
                sum[nxt[curr][i]] += sum[nxt[fail[curr]][i]];
                q.push(nxt[curr][i]);
            } else
                nxt[curr][i] = nxt[fail[curr]][i];
        }
    }
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);

    cin >> n;
    F0R(i, n) {
        cin >> s;
        int c;
        cin >> c;
        insert(c);
    }
    build();
    cin >> s;

    F0R(i, cnt + 1)
        F0R(j, (1 << 14))
            dp[i][j] = -INF;

    dp[0][0] = 0;
    fill(tmp, tmp + cnt + 1, 0);
    iota(to, to + cnt + 1, 0);

    for (char c : s) {
        if (c == '?') {
            R0F(s, (1 << 14) - 1) {
                F0R(p, cnt + 1) {
                    if(dp[p][s] == -INF)
                        continue;
                    F0R(i, 14) {
                        if(s >> i & 1)
                            continue;
                        ckmax(dp[nxt[to[p]][i]][s | 1 << i],
                              dp[p][s] + tmp[p] + sum[nxt[to[p]][i]]);
                    }
                    dp[p][s] = -INF;
                }
            }
            F0R(p, cnt + 1) {
                tmp[p] = 0;
                to[p] = p;
            }
        } else {
            F0R(p, cnt + 1) {
                to[p] = nxt[to[p]][c - 'a'];
                tmp[p] += sum[to[p]];
            }
        }
    }

    ll ans = -INF;
    F0R(p, cnt + 1)
        F0R(s, (1 << 14))
            ckmax(ans, dp[p][s] + tmp[p]);
    cout << ans << endl;
}


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

typedef long long ll;
typedef long double ld;
typedef pair<int, int> ii;
typedef vector<int> vi;
typedef vector<ii> vii;
typedef vector<vi> vvi;
typedef vector<vii> vvii;
#define fi first
#define se second
#define ALL(x) (x).begin(), (x).end()
#define RALL(x) (x).rbegin(), (x).rend()
#define pf push_front
#define eb emplace_back
#define pb push_back
#define mp make_pair
#define mt make_tuple
#define ge(a, b) get<(b)>((a))
#define SZ(x) (x).size()
#define endl '\n'
#define FOR(a, b, c) for (auto(a) = (b); (a) < (c); ++(a))
#define ROF(a, b, c) for (auto(a) = (b); (a) > (c); --(a))
#define F0R(a, b) FOR ((a), 0, (b))
#define R0F(a, b) ROF ((a), (b), -1)
#define CEIL(a, b) ((a) + (b)-1) / (b)
#define CLR(a, b) memset((a), (b), sizeof((a)))
template <typename t> bool ckmin(t& a, const t& b) {return a > b ? a = b, true : false;}
template <typename t> bool ckmax(t& a, const t& b) {return a < b ? a = b, true : false;}

const int MAXN = 1e3 + 7,
          MAXK = 14;
const ll INF = 3e18;

int nxt[MAXN][26], fail[MAXN], nodeCnt, to[MAXN];
ll sum[MAXN], dp[MAXN][1 << MAXK], tmp[MAXN];

void insert(string &s, int cost) {
    int curr = 0;
    for (char c : s) {
        if (!nxt[curr][c - 'a'])
            nxt[curr][c - 'a'] = ++nodeCnt;
        curr = nxt[curr][c - 'a'];
    }
    sum[curr] += cost;
}

void build() {
    queue<int> q;
    F0R(i, MAXK)
        if (nxt[0][i])
            q.push(nxt[0][i]);

    while (!q.empty()) {
        int curr = q.front();
        q.pop();
        F0R(i, MAXK) {
            if (nxt[curr][i]) {
                fail[nxt[curr][i]] = nxt[fail[curr]][i];
                sum[nxt[curr][i]] += sum[nxt[fail[curr]][i]];
                q.push(nxt[curr][i]);
            } else {
                nxt[curr][i] = nxt[fail[curr]][i];
            }
        }
    }
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);

    int n, cost;
    string s;
    cin >> n;
    F0R(i, n) {
        cin >> s >> cost
        insert(s, cost);
    }
    cin >> s;

    build();

    F0R(i, nodeCnt + 1)
        F0R(j, (1 << MAXK))
            dp[i][j] = -INF;

    dp[0][0] = 0;
    fill(tmp, tmp + nodeCnt + 1, 0);
    iota(to, to + nodeCnt + 1, 0);

    for (char c : s) {
        if (c == '?') {
            R0F(s, (1 << MAXK) - 1) {
                F0R(p, nodeCnt + 1) {
                    if(dp[p][s] == -INF)
                        continue;
                    F0R(i, MAXK) {
                        if(s >> i & 1)
                            continue;
                        ckmax(dp[nxt[to[p]][i]][s | 1 << i],
                              dp[p][s] + tmp[p] + sum[nxt[to[p]][i]]);
                    }
                    dp[p][s] = -INF;
                }
            }
            fill(tmp, tmp + nodeCnt + 1, 0);
            iota(to, to + nodeCnt + 1, 0);
        } else {
            F0R(p, nodeCnt + 1) {
                to[p] = nxt[to[p]][c - 'a'];
                tmp[p] += sum[to[p]];
            }
        }
    }

    ll ans = -INF;
    F0R(p, nodeCnt + 1)
        F0R(s, (1 << MAXK))
            ckmax(ans, dp[p][s] + tmp[p]);
    cout << ans << endl;
}


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

typedef long long ll;
typedef long double ld;
typedef pair<int, int> ii;
typedef vector<int> vi;
typedef vector<ii> vii;
typedef vector<vi> vvi;
typedef vector<vii> vvii;
#define fi first
#define se second
#define ALL(x) (x).begin(), (x).end()
#define RALL(x) (x).rbegin(), (x).rend()
#define pf push_front
#define eb emplace_back
#define pb push_back
#define mp make_pair
#define mt make_tuple
#define ge(a, b) get<(b)>((a))
#define SZ(x) (x).size()
#define endl '\n'
#define FOR(a, b, c) for (auto(a) = (b); (a) < (c); ++(a))
#define ROF(a, b, c) for (auto(a) = (b); (a) > (c); --(a))
#define F0R(a, b) FOR ((a), 0, (b))
#define R0F(a, b) ROF ((a), (b), -1)
#define CEIL(a, b) ((a) + (b)-1) / (b)
#define CLR(a, b) memset((a), (b), sizeof((a)))
template <typename t> bool ckmin(t& a, const t& b) {return a > b ? a = b, true : false;}
template <typename t> bool ckmax(t& a, const t& b) {return a < b ? a = b, true : false;}

const int MAXN = 1e3 + 7,
          MAXK = 14;
const ll INF = 3e18;

int nxt[MAXN][26], fail[MAXN], nodeCnt;
ll sum[MAXN], dp[MAXN][1 << MAXK];

void insert(string &s, int cost) {
    int curr = 0;
    for (char c : s) {
        if (!nxt[curr][c - 'a'])
            nxt[curr][c - 'a'] = ++nodeCnt;
        curr = nxt[curr][c - 'a'];
    }
    sum[curr] += cost;
}

void build() {
    queue<int> q;
    F0R(i, MAXK)
        if (nxt[0][i])
            q.push(nxt[0][i]);

    while (!q.empty()) {
        int curr = q.front();
        q.pop();
        F0R(i, MAXK) {
            if (nxt[curr][i]) {
                fail[nxt[curr][i]] = nxt[fail[curr]][i];
                sum[nxt[curr][i]] += sum[nxt[fail[curr]][i]];
                q.push(nxt[curr][i]);
            } else {
                nxt[curr][i] = nxt[fail[curr]][i];
            }
        }
    }
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);

    int n, cost;
    string s;
    cin >> n;
    F0R(i, n) {
        cin >> s >> cost;
        insert(s, cost);
    }
    cin >> s;

    build();

    F0R(i, nodeCnt + 1)
        F0R(j, (1 << MAXK))
            dp[i][j] = -INF;

    dp[0][0] = 0;

    vector<ll> tmp(nodeCnt + 1, 0);
    vi to(nodeCnt + 1);
    iota(ALL(to), 0);

    for (char c : s) {
        if (c == '?') {
            R0F(s, (1 << MAXK) - 1) {
                F0R(p, nodeCnt + 1) {
                    if(dp[p][s] == -INF)
                        continue;
                    F0R(i, MAXK) {
                        if(s >> i & 1)
                            continue;
                        ckmax(dp[nxt[to[p]][i]][s | 1 << i],
                              dp[p][s] + tmp[p] + sum[nxt[to[p]][i]]);
                    }
                    dp[p][s] = -INF;
                }
            }
            fill(ALL(tmp), 0);
            iota(ALL(to), 0);
        } else {
            F0R(p, nodeCnt + 1) {
                to[p] = nxt[to[p]][c - 'a'];
                tmp[p] += sum[to[p]];
            }
        }
    }

    ll ans = -INF;
    F0R(p, nodeCnt + 1)
        F0R(s, (1 << MAXK))
            ckmax(ans, dp[p][s] + tmp[p]);
    cout << ans << endl;
}


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

typedef long long ll;
typedef long double ld;
typedef pair<int, int> ii;
typedef vector<int> vi;
typedef vector<ii> vii;
typedef vector<vi> vvi;
typedef vector<vii> vvii;
#define fi first
#define se second
#define ALL(x) (x).begin(), (x).end()
#define RALL(x) (x).rbegin(), (x).rend()
#define pf push_front
#define eb emplace_back
#define pb push_back
#define mp make_pair
#define mt make_tuple
#define ge(a, b) get<(b)>((a))
#define SZ(x) (x).size()
#define endl '\n'
#define FOR(a, b, c) for (auto(a) = (b); (a) < (c); ++(a))
#define ROF(a, b, c) for (auto(a) = (b); (a) > (c); --(a))
#define F0R(a, b) FOR ((a), 0, (b))
#define R0F(a, b) ROF ((a), (b), -1)
#define CEIL(a, b) ((a) + (b)-1) / (b)
#define CLR(a, b) memset((a), (b), sizeof((a)))
template <typename t> bool ckmin(t& a, const t& b) {return a > b ? a = b, true : false;}
template <typename t> bool ckmax(t& a, const t& b) {return a < b ? a = b, true : false;}

const int MAXN = 1e3 + 7,
          MAXK = 14;
const ll INF = 3e18;

int nxt[MAXN][26], fail[MAXN], nodeCnt, to[MAXN];
ll sum[MAXN], dp[MAXN][1 << MAXK], tmp[MAXN];

void insert(string &s, int cost) {
    int curr = 0;
    for (char c : s) {
        if (!nxt[curr][c - 'a'])
            nxt[curr][c - 'a'] = ++nodeCnt;
        curr = nxt[curr][c - 'a'];
    }
    sum[curr] += cost;
}

void build() {
    queue<int> q;
    F0R(i, MAXK)
        if (nxt[0][i])
            q.push(nxt[0][i]);

    while (!q.empty()) {
        int curr = q.front();
        q.pop();
        F0R(i, MAXK) {
            if (nxt[curr][i]) {
                fail[nxt[curr][i]] = nxt[fail[curr]][i];
                sum[nxt[curr][i]] += sum[nxt[fail[curr]][i]];
                q.push(nxt[curr][i]);
            } else {
                nxt[curr][i] = nxt[fail[curr]][i];
            }
        }
    }
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);

    int n, cost;
    string s;
    cin >> n;
    F0R(i, n) {
        cin >> s >> cost;
        insert(s, cost);
    }
    cin >> s;

    build();

    F0R(i, nodeCnt + 1)
        F0R(j, (1 << MAXK))
            dp[i][j] = -INF;

    dp[0][0] = 0;
    fill(tmp, tmp + nodeCnt + 1, 0);
    iota(to, to + nodeCnt + 1, 0);

    for (char c : s) {
        if (c == '?') {
            R0F(s, (1 << MAXK) - 1) {
                F0R(p, nodeCnt + 1) {
                    if(dp[p][s] == -INF)
                        continue;
                    F0R(i, MAXK) {
                        if(s >> i & 1)
                            continue;
                        ckmax(dp[nxt[to[p]][i]][s | 1 << i],
                              dp[p][s] + tmp[p] + sum[nxt[to[p]][i]]);
                    }
                    dp[p][s] = -INF;
                }
            }
            fill(tmp, tmp + nodeCnt + 1, 0);
            iota(to, to + nodeCnt + 1, 0);
        } else {
            F0R(p, nodeCnt + 1) {
                to[p] = nxt[to[p]][c - 'a'];
                tmp[p] += sum[to[p]];
            }
        }
    }

    ll ans = -INF;
    F0R(p, nodeCnt + 1)
        F0R(s, (1 << MAXK))
            ckmax(ans, dp[p][s] + tmp[p]);
    cout << ans << endl;
}