CF877B Nikita and string TJ

前言的前言

本 TJ 同步发布于洛谷,在线求赞(bushi

前言

蒟蒻第一篇题解,在线求审核大大给过 awa。

如果此题解有什么问题的话欢迎各位大巨佬提出。

题目链接:CF877B

题目类型:dp,一讲就会,一做就废(;′⌒`)。


更新

2021/5/31:因为帮同学调代码添加了新发现的滚动数组问题。


题意简述

给定一个只含有 “a” 和 “b” 的字符串,求这个字符串中最长的子序列,子序列满足以下条件之一:

  1. 全为 “a”。

  2. 全为 “b”。

  3. 开头和结尾是连续的 “a”,中间是连续的 “b”。

字符串长度 ≤ 5000 \leq 5000 5000

题解

算法:动态规划

首先可以发现一个“美丽”的字符串由三个部分组成,所以我们可以开一个二维的 d p dp dp 数组。其含义如下:

  • d p i , 1 dp_{i,1} dpi,1 代表字符串只包含第一部分,从 1 ∼ i 1\sim i 1i 能取到的最长长度;

  • d p i , 2 dp_{i,2} dpi,2 代表字符串包含第一部分和第二部分,从 1 ∼ i 1\sim i 1i 能取到的最长长度;

  • d p i , 3 dp_{i,3} dpi,3 代表字符串包含所有部分,从 1 ∼ i 1\sim i 1i 能取到的最长长度。

因为每个部分都可以是空串,所以最后的答案可以为 d p i , 1 , d p i , 2 , d p i , 3 dp_{i,1},dp_{i,2},dp_{i,3} dpi,1,dpi,2,dpi,3任意一种,取最大值。

然后思考如何转移状态。

首先 d p i , 1 dp_{i,1} dpi,1 是很好求的,我们只需要求解字符串里面有多少个 “a” 就可以了。这个很好想,要求解一个字符串中最长的只包含 “a” 或为空的子序列,只需统计 “a” 的个数,让所有的 “a” 都加入子序列。

当然我们不需要每次都用一个循环求解,可以参照前缀和的方式求解 “a” 的个数:

dp[i][1]=dp[i-1][1]+(a[i]=='a');

(如果这个字符是 “a” 就 + 1 +1 +1,反之不加)

接着考虑 d p i , 2 dp_{i,2} dpi,2 的求法。实际上,我们可以用另一种方式理解前面求 d p i , 1 dp_{i,1} dpi,1,把它当做动态转移方程而并不是一个简单的求前缀和的式子:

  • 如果这个字符是 “a”,那么一定要选,然后再加上前 i − 1 i-1 i1 个字符能拿到的最长的长度。

那么, d p i , 2 dp_{i,2} dpi,2 的解法就呼之欲出了:

dp[i][1]=max(dp[i][1],dp[i][2])+(a[i]=='b');

因为第一部分和第二部分都可以是空串,所以一个全为 “a” 的字符串同样可以作为第二部分的结果,所以需要求最大值。最后的那个 “b” 也是一样的,因为如果这个字符串不是一个全为 “a” 的字符串,就必须以 “b” 结束。所以只有后面是 “b” 才能增加字符串的长度,若答案全是 “a” 那就是 d p i , 1 dp_{i,1} dpi,1,与 d p i , 2 dp_{i,2} dpi,2 的求解是无关的。

d p i , 3 dp_{i,3} dpi,3 方法类似,需要考虑空串的情况,考虑 d p dp dp 前两维的情况;也需要根据结尾的 “a” 判断不为空的最大值,读者自证不难

dp[i][3]=max(dp[i][1],dp[i][2],dp[i][3])+(a[i]=='a');

最后可以看到:

我们只有 " 1 , 3 1,3 1,3 不空, 2 2 2 空"和 " 1 , 3 1,3 1,3 空, 2 2 2 不空"的情况没有考虑到。但是由于第一个部分和第三个部分本质上是一样的,所以这两种情况可以分别对应到另外两种我们已经考虑过的情况——全为 “a” 或全为 “b”。

C o d e Code Code

#include<bits/stdc++.h>
using namespace std;
int dp[5005][4],n;
char a[5005];
int main(){
	scanf("%s",a+1);
	n=strlen(a+1);
	for(int i=1;i<=n;i++){
		dp[i][1]=dp[i-1][1]+(a[i]=='a');
		dp[i][2]=max(dp[i-1][1],dp[i-1][2])+(a[i]=='b');
		dp[i][3]=max(dp[i-1][1],max(dp[i-1][2],dp[i-1][3]))+(a[i]=='a');
	}
	printf("%d",max(dp[n][1],max(dp[n][2],dp[n][3])));
	return 0;
}

关于滚动数组

我有个同学一直没过让 me 帮他调代码,然后我发现他用的是滚动数组。

首先要明确这道题是不能在上述代码中直接修改的。因为如果打滚动,例如求 d p i , 3 dp_{i,3} dpi,3 时需要用到 d p i − 1 , 2 dp_{i-1,2} dpi1,2 的值,但是如果打滚动这个值就已经被更新为 d p i , 2 dp_{i,2} dpi,2 了。

如果要打滚动呢?

很简单,先求 d p i , 3 dp_{i,3} dpi,3,再求 d p i , 2 dp_{i,2} dpi,2,最后求 d p i , 1 dp_{i,1} dpi,1 就阔以了。


最后希望大家文明浏览,否则陶片两行泪 qwq。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值