AC自动机+DP P3041 视频游戏的连击 题解
题意:
贝西在玩一款游戏,该游戏只有三个技能键 “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)个字符,他最多能获得多少分?
题意好像很浅显,就是告诉你一堆模式串,叫你求出限定长度的最大匹配
反向思维是很重要的,就像Poi2000 病毒这道题。
那我们不妨设想:如果已经求出了这个串,该如何匹配?
答案是一直跳son,并对这个节点跳一次fail算答案。
那么我们就可以把每个点的贡献算出来(跳fail能得到的答案),在AC自动机上DP。
设计状态\(f[i][j]\),表示长度为\(i\)的字符串在跳到\(j\)这个点时这个点以及前面(可能是环,注意顺序就好)的最大答案。
那么显然有
\(f[i][tree[j].son[s]]=max(f[i][tree[j].son[s]],ans[tree[j].son[s]]+f[i-1][j])\)
解释一下,对于某个节点进行拓展,可以更新它儿子的f值。 如果取这个儿子进行拓展,答案是自己之前长度为j-1的最大值加上这个儿子的贡献,否组不取,枚举下一个儿子。
边界:f[0][u]=0 长度为零,答案自然为0
满足无后效性的方法:计算某个节点的f值的时候,要用到f[i-1][j],i就从1~maxlen枚举
son的访问顺序倒无所谓,反正用到的son的f是上一轮计算过的,按插入顺序枚举就好。
代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
int read(){
int x=0,pos=1;char ch=getchar();
for(;!isdigit(ch);ch=getchar()) if(ch=='-') pos=0;
for(;isdigit(ch);ch=getchar()) x=x*10+ch-'0';
return pos?x:-x;
}
int n;char s[2001],m[1000051];int f[1000051],dp[1001][10001];int l[2001];
struct ac{
int son[4],fail,end,fl,q[4];
}tree[1000001];
int tot=0;
void insert(int cnt){
int now=0;int len=l[cnt];
for(int i=0;i<len;i++){
if(!tree[now].son[s[i]-'A'+1]){
tree[now].son[s[i]-'A'+1]=++tot;
tree[now].q[s[i]-'A'+1]=1;
memset(tree[tot].son,0,sizeof(tree[tot].son));
tree[tot].fail=0;
}
now=tree[now].son[s[i]-'A'+1];
}
f[now]++;
return;
}
void bfs(){
queue<int>q;
for(int i=1;i<=3;i++){
if(tree[0].son[i]) {
tree[tree[0].son[i]].fail=0;
q.push(tree[0].son[i]);
}
}
while(!q.empty()){
int now=q.front();
q.pop();
for(int i=1;i<=3;i++){
if(tree[now].son[i]){
int nex=tree[now].son[i];
tree[nex].fail=tree[tree[now].fail].son[i];
tree[tree[tree[now].fail].son[i]].fl=nex;
q.push(nex);
}else tree[now].son[i]=tree[tree[now].fail].son[i];
}
f[now]+=f[tree[now].fail];
}
}
int maxlen;
int DP(){
for(int i=0;i<=maxlen;i++){
for(int j=1;j<=tot;j++){
dp[i][j]=-19260817;
}
}
for(int i=1;i<=maxlen;i++){
for(int j=0;j<=tot;j++){
for(int s=1;s<=3;s++){
dp[i][tree[j].son[s]]=max(dp[i][tree[j].son[s]],f[tree[j].son[s]]+dp[i-1][j]);
}
}
}
int mn=0;
for(int i=0;i<=tot;i++){
mn=max(mn,dp[maxlen][i]);
}
return mn;
}
int main(){
n=read();scanf("%d",&maxlen);
tot=0;
for(int i=1;i<=n;i++){
scanf("%s",s);
int len=strlen(s);
l[i]=len;
insert(i);
}
bfs();
printf("%d",DP());
return 0;
}