题意:
给定m个不同的单词,单词由小写字母组成,每个单词都有自身的价值。
现在你要设计一个字符串,字符串的长度不能超过n。
如果字符串里面包含了某个单词,就可以获得相应的价值。
价值可以重复计算。单词可以相互重叠。
输出构造的最大价值的字符串。
如果价值相同,就选择长度最短的,如果长度一样短,那么就选择字典序最小。
数据范围:n<=50,m<=100
解法:
建立ac自动机,
容易想到令d[i][j]表示走i步,当前在j节点上的最大价值,
因为要输出串的方案,还需要记录路径,
因为串长度不超过50,直接开string类的数组path[i][j]记录整个路径的串,比较的时候也是直接比较就行了.
在ac自动机上dp即可.
注意特判最大价值等于0的情况,这时候应该输出空串.
code:
#include<bits/stdc++.h>
using namespace std;
const int maxm=2e3+5;
char s[maxm][15];
int n,m;
struct AC{
int a[maxm][26],fail[maxm],val[maxm],tot=0;
void init(){
for(int i=0;i<=tot;i++){
memset(a[i],0,sizeof a[i]);
val[i]=fail[i]=0;
}
tot=0;
}
void add(char *s,int x){
int len=strlen(s);
int node=0;
for(int i=0;i<len;i++){
int v=s[i]-'a';
if(!a[node][v])a[node][v]=++tot;
node=a[node][v];
}
val[node]+=x;
}
void build(){//构造fail
queue<int>q;
for(int i=0;i<26;i++){
if(a[0][i]){
q.push(a[0][i]);
}
}
while(!q.empty()){
int x=q.front();
q.pop();
val[x]+=val[fail[x]];//可以重复叠加
for(int i=0;i<26;i++){
if(a[x][i]){
fail[a[x][i]]=a[fail[x]][i];
q.push(a[x][i]);
}else{
a[x][i]=a[fail[x]][i];
}
}
}
}
//
int d[55][maxm];//d[i][j]表示走i步当前在j节点的最大价值.
string path[55][maxm];
void solve(){
//init
for(int i=0;i<=n;i++){
for(int j=0;j<=tot;j++){
d[i][j]=-1;
path[i][j]="";
}
}
//dp
d[0][0]=0;
for(int i=0;i<n;i++){
for(int j=0;j<=tot;j++){
if(d[i][j]==-1)continue;
for(int k=0;k<26;k++){
int x=a[j][k];
int temp=d[i][j]+val[x];
string p=path[i][j]+(char)(k+'a');
if(temp>d[i+1][x]){
d[i+1][x]=temp;
path[i+1][x]=p;
}else if(temp==d[i+1][x]){
if(p<path[i+1][x]){
path[i+1][x]=p;
}
}
}
}
}
int ma=0;
for(int j=0;j<=tot;j++){
ma=max(ma,d[n][j]);
}
if(ma==0){//特判ma=0的情况
cout<<endl;return ;
}
string ans;
for(int i=0;i<=n;i++){
for(int j=0;j<=tot;j++){
if(d[i][j]==ma){
if(ans==""||ans>path[i][j]){
ans=path[i][j];
}
}
}
if(ans!="")break;
}
cout<<ans<<endl;
}
}ac;
signed main(){
int T;scanf("%d",&T);
while(T--){
ac.init();
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
scanf("%s",s[i]);
}
for(int i=1;i<=m;i++){
int x;scanf("%d",&x);
ac.add(s[i],x);
}
ac.build();
//
ac.solve();
}
return 0;
}