[Luogu P3041] [BZOJ 2580] [USACO12JAN]视频游戏的连击Video Game Combos

洛谷传送门
BZOJ传送门

题目描述

贝西在玩一款游戏,该游戏只有三个技能键 ABC “ A ” “ B ” “ C ” 可用,但这些键可用形成 N N 种(1N20)特定的组合技。第 i i 个组合技用一个长度为1 15 15 的字符串 Si S i 表示。

当贝西输入的一个字符序列和一个组合技匹配的时候,他将获得 1 1 分。特殊的,他输入的一个字符序列有可能同时和若干个组合技匹配,比如N=3时, 3 3 种组合技分别为”ABA”, “ CB C B ”, 和” ABACB A B A C B ”,若贝西输入” ABACB A B A C B ”,他将获得 3 3 分。

若贝西输入恰好K ( 1K1,000 1 ≤ K ≤ 1 , 000 )个字符,他最多能获得多少分?

输入输出格式

输入格式

第一行两个正整数, 表示 NK N 、 K

接下来 N N 行, 每行一个由A,B,C构成的字符串, 表示一个组合技的序列。

输出格式

一行一个正整数, 表示最多能获得的分数。

输入输出样例

输入样例#1:
3 7 
ABA 
CB 
ABACB 
输出样例#1:
4 

解题分析

AC A C 自动机上 DP D P 的套路题。

先建出 AC A C 自动机, 构出 trie t r i e 树, 在每个合法字符串的末端打上标记。

注意一个大串可能包含多个合法小串, 所以将每个合法的 fail f a i l 转移的答案累加到当前点(跳 fail f a i l 后得到的是一个合法后缀, 而转移是通过前缀转移, 不会重复。)。

然后 dp[i][j] d p [ i ] [ j ] 表示第 i i 位, 当前在trie树上第 j j 个节点的答案,通过三个字符的转移取max即可。

代码如下:

#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cctype>
#include <cmath>
#include <algorithm>
#include <queue>
#define R register
#define IN inline
#define gc getchar()
#define W while
#define MX 100500
#define INF 100000000
int num, k, cnt, root, ans;
char buf[21];
int to[MX][3], ct[MX], fail[MX], dp[1050][350];
std::queue <int> q;
namespace AC
{
    void insert(char* str, R int len)
    {
        R int now = root;
        for (R int i = 0; i < len; ++i)
        {
            if(!to[now][str[i] - 'A']) to[now][str[i] - 'A'] = ++cnt;
            now = to[now][str[i] - 'A'];
        }
        ct[now]++;
    }
    void build()
    {
        R int now;
        for (R int i = 0; i < 3; ++i)
        if(to[root][i]) q.push(to[root][i]);
        W (!q.empty())
        {
            now = q.front(); q.pop();
            for (R int i = 0; i < 3; ++i)
            {
                if(to[now][i])
                {
                    fail[to[now][i]] = to[fail[now]][i];
                    q.push(to[now][i]);
                }
                else to[now][i] = to[fail[now]][i];//为了传递fail数组, 得到第一个符合条件的答案
            }
            ct[now] += ct[fail[now]];
        }
    }
    void DP()
    {
        R int i, j, o;
        for (i = 0; i <= k; ++i)
        for (j = 1; j <= cnt; ++j)
            dp[i][j] = -INF;
        for (i = 1; i <= k; ++i)
        for (j = 0; j <= cnt; ++j)
        {
            for (o = 0; o < 3; ++o)
            dp[i][to[j][o]] = std::max(dp[i][to[j][o]], dp[i - 1][j] + ct[to[j][o]]);
        }
        for (R int i = 0; i <= cnt; ++i) ans = std::max(ans, dp[k][i]);
    }
}
int main(void)
{
    scanf("%d%d", &num, &k);
    W (num--) scanf("%s", buf), AC::insert(buf, std::strlen(buf));
    AC::build(), AC::DP();
    printf("%d", ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值