#include <iostream>#include <cstdio>#include <cstring>#include <string>#include <vector>#include <queue>using namespace std;#define pb push_back#define INF (1<<30)struct node{ int son[26],fail; int cnt; char key; node(){memset(son,0,sizeof(son));fail=0;cnt=0;} node(char k):key(k){memset(son,0,sizeof(son));fail=0;cnt=0;}};vector<node> ac;int hash[256];int dp[51][2000];int n,m,T;string key[51][2000];//我采取了暴力做法,记录每条路径上的全部字符.另外一种做法是将字符串颠倒,只记录前驱.void init() { for(int i=0;i<26;++i) hash['a'+i]=i;}void insert(const char *str,int num) { int p=0,i=0; while(str[p]) { if(!ac[i].son[hash[str[p]]]) { ac[i].son[hash[str[p]]]=ac.size(); ac.pb(node(str[p])); } i=ac[i].son[hash[str[p]]]; ++p; } ac[i].cnt=num;}void getFail() { queue<int> qu; qu.push(0); while(!qu.empty()) { int cur=qu.front();qu.pop(); for(int i=0;i<26;++i) { int nex=ac[cur].son[i]; int p=ac[cur].fail; if(nex) {//以下这部分实际上提供了另一种求fail的方法 qu.push(nex); if(~p) ac[nex].fail=ac[p].son[i],ac[nex].cnt+=ac[ac[p].son[i]].cnt; else ac[nex].fail=0; } else {//新的fail求法的关键是这一步,它保证每个儿子指针都指向某个节点 if(~p) ac[cur].son[i]=ac[p].son[i];//空儿子不是指向根,就是指向实儿子 } } }}char str[101][20];int main() { ios::sync_with_stdio(false); init(); cin >> T; while(T--) { cin >> n >> m; ac.clear(); ac.pb(node('a')); ac[0].fail=-1; for(int i=0;i<m;++i) cin >> str[i]; for(int i=0;i<m;++i) { int val; cin >> val; insert(str[i],val); } getFail(); for(int i=0;i<=n;++i) for(int j=0;j<ac.size();++j) dp[i][j]=-1,key[i][j].resize(60,'z'); dp[0][0]=0; key[0][0]=""; int ans=0,ansi=0,ansj=0; for(int i=0;i<n;++i) for(int j=0;j<ac.size();++j) { if(dp[i][j]==-1) continue; for(int p=0;p<26;++p) { int son=ac[j].son[p]; if(dp[i+1][son]<dp[i][j]+ac[son].cnt ||(dp[i+1][son]==dp[i][j]+ac[son].cnt&&key[i+1][son]>key[i][j]+char('a'+p))) { dp[i+1][son]=dp[i][j]+ac[son].cnt; key[i+1][son]=key[i][j]+char('a'+p); } if(dp[i+1][son]>ans||(dp[i+1][son]==ans&&i+1==ansi&&key[i+1][son]<key[ansi][ansj])) { ans=dp[i+1][son]; ansi=i+1;ansj=son; } } } cout << key[ansi][ansj] << endl; } return 0;}