题意:贝西在玩一款游戏,该游戏只有三个技能键 “A”“B”“C”可用,但这些键可用形成N种(1 <= N<= 20)特定的组合技。第i个组合技用一个长度为1到15的字符串S_i表示。
当贝西输入的一个字符序列和一个组合技匹配的时候,他将获得1分。特殊的,他输入的一个字符序列有可能同时和若干个组合技匹配,比如N=3时,3种组合技分别为"ABA", “CB”, 和"ABACB",若贝西输入"ABACB",他将获得3分。
若贝西输入恰好K (1 <= K <= 1,000)个字符,他最多能获得多少分?
题解:这道题和文本生成器(题目链接)类似,没有刷过的可以尝试一下,感觉这种dp都是套路。
我们用dp[i][j]表示已经匹配的长度为i,当前在j节点的最大得分,然后从父亲往儿子转移即可,要注意的是,如果字符串的子串如果也是能得分的,我们应该将得分加到u中,表示该节点的得分
#include<bits/stdc++.h>
using namespace std;
const int MAXNODE = 1e6+50;
const int INF = 0x3f3f3f3f;
struct Trie{
int nxt[MAXNODE][3],fail[MAXNODE],cnt[MAXNODE];
int dp[1050][1050];
int sz;
void Insert(char *s){
int root=0,len=strlen(s);
for(int i=0;i<len;i++){
if(!nxt[root][s[i]-'A']) nxt[root][s[i]-'A'] = ++sz;
root = nxt[root][s[i]-'A'];
}
cnt[root]++;
}
void Build(){
queue<int> que;
for(int i=0;i<3;i++)
if(nxt[0][i])
que.push(nxt[0][i]);
while(!que.empty()){
int u = que.front();
que.pop();
for(int i=0;i<3;i++){
if(nxt[u][i]) que.push(nxt[u][i]),fail[nxt[u][i]] = nxt[fail[u]][i];
else nxt[u][i] = nxt[fail[u]][i];
}
cnt[u] += cnt[fail[u]];//该点的贡献值应该加上其子串(后缀)的得分
}
}
void Solve(int len){
memset(dp,-INF,sizeof(dp));
dp[0][0]=0;
for(int i=1;i<=len;i++)
for(int j=0;j<=sz;j++)
for(int k=0;k<3;k++)
dp[i][nxt[j][k]] = max(dp[i][nxt[j][k]],dp[i-1][j]+cnt[nxt[j][k]]);
int res = 0;
for(int i=0;i<=sz;i++)
res = max(res,dp[len][i]);
printf("%d\n",res);
}
}Automata;
char str[20];
int main(){
int n,k;
scanf("%d%d",&n,&k);
for(int i=0;i<n;i++) scanf("%s",str),Automata.Insert(str);
Automata.Build();
Automata.Solve(k);
return 0;
}