今天选择的题目是Div3比赛的最后一题,也是最难的一道题。选这道题的主要原因是帮助大家建立信心,因为有些小伙伴给我反应说之前选择的题目有些难了,觉得自己可能应付不了codeforces的题目。所以今天特地选了Div3比赛当中的最难题来给大家一点信心。

Div3比赛是难度最低的比赛,面向的是算法的初学者,非常适合大家作为入门练习。由于是最后一道题,所以这题的通过人数不是很多,大概是530人。但是这题并不很难,我感觉大约和我们Div2中的C题相当。


codeforces div 赛制 codeforces比赛难度_子序列

废话不多说了,让我们直接看题吧。

题意

给定一个只有abc和?的字符串,其中的?可以被任意替换成a、b或者c。我们假设?一共有k个,这样我们一共可以得到个不同的字符串。

要求所有这些字符串当中拥有的abc子序列的个数,子序列是指原串在不改变元素相对顺序的情况下通过删除元素得到的子集。比如说baacbc拥有两个abc的子序列,分别是下标组合(2,5,6)和(3,5,6)。

由于最终的数量可能会很大,要求这个结果对取模。

样例

输入一共有两行,第一行给定一个整数n(),表示字符串的长度。第二行输入长度为n的字符串,要求返回abc子序列的个数。

codeforces div 赛制 codeforces比赛难度_字符串_02

对于第一个样例的解释:

codeforces div 赛制 codeforces比赛难度_子序列_03

题解

这题看起来比较复杂,想要直接想出解法来正面攻破还是不太容易的。所以我们先把一些条件放一放,先从最简单的思路入手。

最简情况

首先我们需要解决一个问题,就是当不存在?的时候,我们如何求出abc子序列的个数呢?比如acabac,我们怎么求出其中abc子序列的数量呢?答案是2,这个2是怎么来的呢?

因为要保证b出现在a的后面,c出现在b的后面,我们当然可以直接用三重循环去枚举所有的组合,但这显然不是最好的方法。我们仔细想一下,这是一个有限制的组合问题,很容易发现每一个字母b都可以和它之前的所有a组成ab的配对,同样每个字母c也可以和之前的每个序列ab组成abc的子序列。

那么我们可以使用动态规划的思路来求解,我们用三个变量d[0], d[1], d[2],分别维护a、ab和abc串的数量。当我们遇到a的时候,d[0]加1,遇到b的时候呢?d[1]加1吗?显然不对,因为b可以和之前的每一个a都组成ab,所以d[1]应该加的不是1而是d[0]。同样,遇到c的时候,也一样,d[2]应该加上d[1]。

这样我们就可以求出在没有?时满足条件的子序列的数量了。

d = [0, 0, 0]

for i in range(n):
    if s[i] == 'a':
        d[0] += 1
    elif s[1] == 'b':
        d[1] += d[0]
    else:
        d[2] += d[1]

进阶使用

现在我们已经解决了没有?出现时的情况,那么加上?的话应该怎么办呢?

我们进一步来分析,对于每一个?来说它其实都有三种选择。比如a?c,展开之后会有aac,abc,acc这三种情况。那么我们能不能在遇到?的时候把这三种情况全部都考虑进去呢?

当我们遇到?的时候,我们把d数组拷贝成三份,分别用来存储?=a, ?=b 和 ?=c的情况。我们先把数组d拷贝成d1、d2和d3。我们用这三个数组代表?取三种不同取值的情况,最后再把它们合并到一起,变成新的数组d_new。

因为d1, d2和d3其实就是d,所以我们把它们最后整合在一起之后依然可以用d来表示。

codeforces div 赛制 codeforces比赛难度_codeforces div 赛制_04

到这里都没有问题对吧,其实这里藏了一个trick。这个trick非常隐蔽,很难发现。就是我们在?=a的情况下,我们这里d1[0] += 1其实是不对的,我们要加的不是1。我们来举一个例子就明白了,比如a??c。

在这个字符串当中我们存在两个?,对于第一个?而言,它是a的时候d[0] += 1这没有问题。但是当遇到第二个?的时候问题就来了,对于第一个问号等于a的情况而言,由于第二个问号有三种取值,那么带来的a的数量其实应该是3,而不是1。我们只加了1就是错误的。我们也可以反过来理解,对于第一个问号,我们整合了三种可能性。当我们第二个问号设置为a的时候,其实是背后是第一个问号带来的3种可能,所以这里要加的就不是1而是3。如果我们有3个问号呢?第三个问号显然加的就是9了。也就是说对于第k个问号而言,。

这个trick注意到了,那么AC也就是顺理成章的事了。

n = int(input())
s = input()

ret = 0
Mod = int(1e9+7)

dp = [0, 0, 0]
cnt = 1

for i in range(n):
    if s[i] == 'a':
        dp[0] += cnt
    elif s[i] == 'b':
        dp[1] += dp[0]
    elif s[i] == 'c':
        dp[2] += dp[1]
    else:
        dp[2] = (3 * dp[2] + dp[1]) % Mod
        dp[1] = (3 * dp[1] + dp[0]) % Mod
        dp[0] = (3 * dp[0] + cnt) % Mod
        cnt = (cnt * 3) % Mod


print(dp[2] % Mod)

今天选择的是div3比赛当中通过人数最少,也就是最难的一题。但我们做下来会发现其实也就用到了最基础的动态规划的思想,虽然不容易想到解,但是难度其实还可以。

所以如果你想要提升自己的算法能力,但是又担心题目太难自己无法胜任的话,可以考虑做一做div3的一些比赛。相信我不会太难的。