SPOJ VIDEO Video game combos 题解
SP10502 VIDEO - Video game combos
AC 自动机上的动态规划。
由题目中这句话,不难想到 AC 自动机:
\(s_i\) 在 \(t\) 中出现一次指的是 \(s_i\) 是 \(t\) 从某个位置起的连续子串。如果 \(s_i\) 从 \(t\) 的多个位置起都是连续子串,那么算作 \(s_i\) 出现了多次。
考虑到每个位置有多种转移情况,且满足最优子结构性质,使用动态规划。观察后发现确定一个位置需要两个参数:目前是主串的的 \(i\) 个字符,目前在 AC 自动机中的位置 \(j\)。易得转移方程:
\[dp[i+1][trie[j][k]]=\max(dp[i+1][trie[j][k]],dp[i][j]+h[trie[j][k]])
\]
这里 \(k\) 是枚举 AC 自动机中的出边,\(h[i]\) 表示在 AC 自动机中的位置 \(i\) 匹配可以得到的分数。实质上,这是一个根据现在的状态推出后面状态的方程,所以 \(\max\) 里会有 \(dp[i+1][trie[j][k]]\) 这一项,而后面一项则是目前枚举的状态转移之后的得分。
这里把 \(h[i]\) 预处理出来了,实际上不预处理应该也是可以的。
注意状态需要初始化为负无穷,很明显是不能从不可能的状态转移的。
#include <bits/stdc++.h>
using namespace std;
int n,k,ans,trie[310][3],ap[310],fail[310],que[310],f[1010][310],h[310],cnt=0;
char str[310];
void insert(char str[])
{
int l=strlen(str),root=0;
for(int i=0;i<l;i++)
{
int id=str[i]-'A';
if(!trie[root][id])trie[root][id]=++cnt;
root=trie[root][id];
}
ap[root]++;
}
void build_ac()
{
int head=0,tail=0;
fail[0]=0;
for(int i=0;i<3;i++)
if(trie[0][i])que[tail++]=trie[0][i];
while(head<tail)
{
int now=que[head];
for(int i=0;i<3;i++)
if(trie[now][i])fail[trie[now][i]]=trie[fail[now]][i],que[tail++]=trie[now][i];
else trie[now][i]=trie[fail[now]][i];
head++;
}
}
int main()
{
scanf("%d%d",&n,&k);
for(int i=0;i<n;i++)
{
scanf("%s",str);
insert(str);
}
build_ac();
for(int i=0;i<=cnt;i++)
for(int j=i;j!=0;j=fail[j])h[i]+=ap[j];
for(int i=0;i<=k;i++)
for(int j=0;j<=cnt;j++)
f[i][j]=-99999999;
f[0][0]=0;
for(int i=0;i<k;i++)
for(int j=0;j<=cnt;j++)
for(int k=0;k<3;k++)
f[i+1][trie[j][k]]=max(f[i+1][trie[j][k]],f[i][j]+h[trie[j][k]]);
for(int i=0;i<=cnt;i++)
ans=max(ans,f[k][i]);
printf("%d",ans);
return 0;
}

浙公网安备 33010602011771号