简化价值计算:对某个字符串,先扫描一遍,出现narek中的字符则该字符串花销cost[i]增加1
当成功连成“narek”时,将价值增加10
由于关心分别以n a r e k结尾的字符串的价值可以用来转移,故用dp[i][j]表示第1到 i 个字符串拼成的结尾是 j 的"nareknarek..."串的价值
dp数组计算方式:对一个字符串 i,如果不选,则dp[i][j]=dp[i-1][j],j依次是n a r e k
如果选了,则枚举选了这个字符串后,开头的字符是什么(n a r e k依次枚举),从这个字符串第一个出现选定的开头字符的位置开始,往后查看"nareknareknarek...."序列,如果找到了k将价值加10(每个字符串价值初始为0),最后停在字母 j 上,则dp[i][j]可以被:dp[i-1][ j在narek中的上一个字母 ]+当前字符串的价值+cost[i]更新
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define ll long long
const ll maxn=1e3+5,inf=1e9;
ll n,m;
ll cost[maxn],dp[maxn][10];
char word[10]={'@','n','a','r','e','k'};
int main()
{
freopen("D:\\in.txt","r",stdin);
//ios::sync_with_stdio(0);cin.tie(0);
ll T;cin>>T;
while(T--){
cin>>n>>m;
for(ll j=1;j<=5;j++){
dp[0][j]=-inf;
}
dp[0][5]=0;
for(ll i=1;i<=n;i++){
string s;cin>>s;
cost[i]=0;
//计算cost
for(ll j=0;j<m;j++){
for(ll k=1;k<=5;k++) {
if(s[j]==word[k]) cost[i]++;
}
}
for(ll j=1;j<=5;j++){
dp[i][j]=dp[i-1][j];
}
//进行dp
for(ll start=1;start<=5;start++){
ll cur=start;
ll v=0;
for(ll j=0;j<m;j++){
if(s[j]==word[cur]){
if(cur==5){
v+=10;
cur=1;
}else {
cur++;
}
}
}
//if(start==1) printf("!!%lld!!\n",v);
v-=cost[i];
ll lst=start-1;
if(lst==0) lst=5;
cur--;
if(cur==0) cur=5;
dp[i][cur]=max(dp[i][cur],dp[i-1][lst]+v);
//这里发现,如果没有找到start对应的字符,这样更新也是合理的
}
}
/*
//输出dp table进行调试
for(ll i=1;i<=n;i++){
for(ll k=1;k<=5;k++){
//printf("dp[%lld][%lld]=%lld\n",i,k,dp[i][k]);
}
}*/
ll ans=-inf;
for(ll i=1;i<=5;i++){
ans=max(ans,dp[n][i]);
}
cout<<ans<<"\n";
}
return 0;
}