DP 从放弃到拿捏:一份持续更新的动态规划题解清单(一)

目录

一、题目引入

输入描述

输出描述

二、思路分析

[1. 暴力法不可行](#1. 暴力法不可行)

[2. 动态规划的引入](#2. 动态规划的引入)

[1. 状态表示](#1. 状态表示)

2.状态转移方程

3.空间优化

[3. 边界与数据类型](#3. 边界与数据类型)

三、代码实现

四、复杂度分析


题目链接:mari和shiny

一、题目引入

mari 每天都非常 shiny,她的目标是把正能量传达到世界的每个角落!有一天,她得到了一个仅由小写字母组成的字符串。她想知道,这个字符串有多少个 "shy" 的子序列?

子序列 :不要求连续,但顺序必须与原串一致。例如,字符串 "shy" 本身有一个子序列 "shy";字符串 "shhsyy" 中,'s''h''y' 各自出现两次,可以组成多少个 "shy" 呢?稍加枚举就能发现,答案是 4(每个 's' 可以和它后面的任意 'h''y' 组合,但要注意顺序)。

输入描述

  • 第一行一个正整数 n(1 ≤ n ≤ 300000),代表字符串长度。

  • 第二行一个长度为 n 的仅由小写字母组成的字符串。

输出描述

  • 一个正整数,代表子序列 "shy" 的数量。

二、思路分析

1. 暴力法不可行

最直接的想法是枚举所有三元组 (i, j, k) 满足 i < j < ks[i]=='s's[j]=='h's[k]=='y'。三重循环显然不可行(n 最大 30 万),即使优化到 O(n²) 也会超时。我们需要一种 O(n) 甚至 O(1) 空间的算法。

2. 动态规划的引入

仔细观察,我们要统计的是形如 's' → 'h' → 'y' 的三元组个数,且顺序与原串一致。这是一个多状态的线性dp:我们依次读取字符,每读到一个字符,就考虑它能如何延续已有的部分子序列。

一种经典的动态规划思路是:用dp表记录当前已经形成的不同前缀子序列的个数

1. 状态表示

s[i] 表示:字符串 str 中 [0, i] 区间内,有多少个"s"

h[i] 表示:字符串 str 中 [0, i] 区间内,有多少个 "sh"

y[i] 表示:字符串 str 中 [0,i] 区间内,有多少个 "shy"

2.状态转移方程

3.空间优化

直接用有限的变量来进行状态表示

  • cnt_s 表示到目前为止,以当前字符结尾的 's' 子序列的个数(其实就是字符 's' 出现的次数)。

  • cnt_sh 表示到目前为止,以当前字符结尾的 "sh" 子序列的个数。

  • cnt_shy 表示到目前为止,以当前字符结尾的 "shy" 子序列的个数。

当我们遍历到新字符 c 时:

  • 如果 c == 's':这个 's' 可以作为一个新的 's' 子序列的开始,所以 cnt_s += 1

  • 如果 c == 'h':这个 'h' 可以和之前所有的 's' 组成新的 "sh" 子序列,所以 cnt_sh += cnt_s

  • 如果 c == 'y':这个 'y' 可以和之前所有的 "sh" 组成新的 "shy" 子序列,所以 cnt_shy += cnt_sh

遍历结束后,cnt_shy 就是答案。

3. 边界与数据类型

  • 初始值全部为 0。

  • 答案可能很大:最坏情况字符串全是 's''h''y' 中的一种?实际上如果字符串由这三个字母组成,且顺序允许,最大子序列数可能达到组合数级别,比如 's'*100000 + 'h'*100000 + 'y'*100000,那么 's' 有 10^5 个,'h' 也有 10^5,每个 'y' 可以和前面所有的 "sh" 组合,数量级约为 10^15,会超过 32 位 int 范围,因此必须使用 64 位整数(如 C++ 的 long long)。

三、代码实现

cpp 复制代码
#include <iostream>
#include <string>
using namespace std;

int n;
string str;

int main() {
	cin >> n >> str;
	long long s = 0, h = 0, y = 0;
	for (int i = 0; i < n; i++)
	{
		char ch = str[i];
		if (ch == 's') s++;
		else if (ch == 'h') h += s;
		else if (ch == 'y') y += h;
	}
	cout << y << endl;
	return 0;
}

四、复杂度分析

  • 时间复杂度:O(n),仅需一次遍历字符串。

  • 空间复杂度:O(1),只用了几个辅助变量。


那么本期的内容就到这里了,觉得有收获的同学们可以给个点赞、评论、关注、收藏哦,谢谢大家。

相关推荐
Rabitebla21 小时前
【C++】string 类:原理、踩坑与对象语义
linux·c语言·数据结构·c++·算法·github·学习方法
小雅痞1 天前
[Java][Leetcode middle] 167. 两数之和 II - 输入有序数组
java·算法·leetcode
CN-Dust1 天前
【C++】输入cin例题专题
java·c++·算法
数模竞赛Paid answer1 天前
2025年MathorCup数学建模A题汽车风阻预测解题文档与程序
算法·数学建模·mathorcup
Old Uncle Tom1 天前
OpenClaw 记忆系统 -- 记忆预加载
java·数据结构·算法·agent
会编程的土豆1 天前
洛谷题单入门1 顺序结构
数据结构·算法·golang
生信碱移1 天前
PACells:这个方法可以鉴定疾病/预后相关的重要细胞亚群,作者提供的代码流程可以学习起来了,甚至兼容转录组与 ATAC 两种数据类型!
人工智能·学习·算法·机器学习·数据挖掘·数据分析·r语言
workflower1 天前
具身智能行业应用-生活服务业
大数据·人工智能·机器人·动态规划·生活
智者知已应修善业1 天前
【51单片机中的打飞机设计】2023-8-25
c++·经验分享·笔记·算法·51单片机
智者知已应修善业1 天前
【51单片机按键调节占空比3位数码管显示】2023-8-24
c++·经验分享·笔记·算法·51单片机