1.题目
问题描述
Cion 病毒入侵了 X 公司的数据库,将硬盘数据全部加密了,而解锁这些数据需要花费 Cion 币,因此你需要尽可能多地获取 Cion 币。每次加密完一份数据,Cion 病毒会留下一个 01 组成的字符串,你可以对这些字符串 s 进行任意次数以下三种操作:
- 使用 0 替换子串 00 并获得 a 枚 Cion 币。
- 使用 1 替换子串 11 并获得 b 枚 Cion 币。
- 从任意位置删除 0 并支付 c 枚 Cion 币。
Cion 为了加大解锁难度,要求连续两次操作的编号奇偶性不能相同。
为了赶紧修复数据,公司同意你向银行暂时借用任意枚 Cion 币,但结束时必须归还。操作按上面给出的顺序用整数 1 - 3 编号。请计算出你可以获得的最大 Cion 币是多少。
输入格式
第一行输入 4 个整数 n, a, b, c (1 ≤n≤ 10^5, 1≤ a,b,c ≤10^5).
第二行输入一个长度为 n,由 01 组成的字符串 s
保证所有测试用例的 n 的总和不超过 2*10^5
输出格式
每个测试用例输出一个整数表示你可以获得的最大 Cion 币是多少
输入样例 1
5 2 2 1
01101
输出样例 1
3
输入样例 2
6 4 3 5
110001
输出样例 2
11
输入样例 3
6 3 2 1
011110
输出样例 3
4
在第一个 Case 中,操作的最佳序列是 01101 -> 0101 -> 011 -> 01,操作顺序为 2, 3, 2,利润为 3。
2.思路
深度优先搜索(DFS)+动态规划(缓存)
2.1. 状态定义:
我们使用 dfs(s, flag, a, b, c)
函数来递归地尝试从字符串 s
中获得最多的 Cion 币,其中:
-
s
:当前处理的字符串。 -
flag
:上一操作的编号(1,2,3),它决定了当前可以进行哪些操作。用于确保操作的编号奇偶性交替进行。flag == -1
表示尚未进行操作,起始状态。flag == 1
表示上一操作是奇数,接下来只能执行操作2。flag == 2
表示上一操作是偶数,接下来只能执行操作1或3。
2.2. 递归展开:
递归函数 dfs
会尝试对每个可能的位置进行操作,并递归处理字符串。每次操作会根据当前的 flag
决定是否能进行特定的操作。
-
操作1(替换“00”):
- 如果
s[i] == '0'
且s[i+1] == '0'
,且上一操作不是奇数(flag != 1
),那么我们可以执行操作1,替换00
为0
,并递归调用dfs
。 - 操作后的结果通过递归返回,更新当前最大 Cion 币数。
- 如果
-
操作2(替换“11”):
- 如果
s[i] == '1'
且s[i+1] == '1'
,且上一操作不是偶数(flag != 2
),那么我们可以执行操作2,替换11
为1
,并递归调用dfs
。 - 操作后的结果通过递归返回,更新当前最大 Cion 币数。
- 如果
-
操作3(删除“0”):
- 如果
s[i] == '0'
,且上一操作不是奇数(flag != 1
),那么我们可以执行操作3,删除字符0
,并递归调用dfs
。 - 删除一个
0
时,会支付c
枚 Cion 币。
- 如果
2.3. 递归返回值:
-
每次递归返回的值包含两个部分:
final_s
:操作后的字符串。虽然题目要求的结果并不涉及这个值,但我们保留它以便调试或理解操作的最终结果。res
:表示当前递归状态下,能够获得的最大 Cion 币数。
2.4. 缓存优化:
-
为了避免重复计算相同的子问题,使用
@lru_cache(maxsize=None)
对dfs
函数进行缓存。lru_cache
将会缓存已经计算过的dfs(s, flag)
的结果,当相同的s
和flag
再次出现时,直接返回缓存的结果,避免重复计算,显著提高性能。
2.5. 主函数:
主函数 solution(n, a, b, c, s)
初始化递归调用,设置初始的 flag = -1
(表示没有进行任何操作),并传入字符串 s
。最后返回最大 Cion 币数 res
。
3.代码
from functools import lru_cache #对子问题结果进行缓存,从而避免重复计算
@lru_cache(maxsize = None) # 使用 缓存机制 记录已经计算过的 s 和 flag 状态。
def dfs(s, flag, a, b, c): # flag 表示上一操作的编号(操作的奇偶性)
res = 0 # 当前最大收益
final_s = s # 记录当前最优路径的字符串状态
for i in range(len(s)):
# 替换'11'为'1'
if i < len(s) - 1 and s[i] == '1' and s[i + 1] == '1' and flag != 2: # 上一次操作编号不能是偶数
ss, result = dfs(s[:i]+s[i+1:],2,a,b,c) # 使用深度优先搜索处理新字符串 s[:i] + s[i+1:],即去掉一个 1
if res < result + b:
final_s, res = ss, result + b
# 替换'00'为'0'
elif i < len(s) - 1 and s[i] == '0' and s[i + 1] == '0' and flag != 1: # 上一次操作编号不能是偶数
ss, result = dfs(s[:i]+s[i+1:],1,a,b,c) # 使用深度优先搜索处理新字符串 s[:i] + s[i+1:],即去掉一个 1
if res < result + a:
final_s, res = ss, result + a
# 删除'0'
elif s[i] == '0' and flag != 1: # 上一次操作编号不能是偶数
ss, result = dfs(s[:i]+s[i+1:],1,a,b,c) # 使用深度优先搜索处理新字符串 s[:i] + s[i+1:],即去掉一个 1
if res < result - c:
final_s, res = ss, result - c
return final_s, res
def solution(n, a, b, c, s):
# Please write your code here
final_s, res = dfs(s, -1, a, b, c) # flag初始化为-1,第一次操作可以是1,2,3任意一个
return res
if __name__ == "__main__":
# You can add more test cases here
print(solution(5, 2, 2, 1, "01101") == 3 )
print(solution(6, 4, 3, 5, "110001") == 11 )
print(solution(6, 3, 2, 1, "011110") == 4 )
@lru_cache(maxsize=None)
是 Python 中 functools
模块的一个装饰器,它用于对函数进行缓存,从而提高函数的性能,特别是在需要多次调用同一函数时。
具体作用:
- 缓存函数的返回值: 当函数被调用时,
lru_cache
会记录输入参数与返回值的对应关系。如果函数使用相同的参数再次被调用,lru_cache
会直接返回缓存的结果,而不是重新计算。这有助于避免重复计算相同的结果,提高程序的效率。 - LRU (Least Recently Used) 策略:
lru_cache
使用最少最近使用(LRU)算法来管理缓存。如果缓存的条目数量超过了maxsize
设置的限制(默认为 128),它会删除最久未使用的条目,以释放内存。maxsize=None
意味着没有缓存大小限制,缓存将一直存储所有计算结果,直到程序退出。
例子:
假设有一个递归函数,我们希望避免对同一输入值的重复计算,可以使用 @lru_cache
来优化它。
from functools import lru_cache
@lru_cache(maxsize=None)
def fibonacci(n):
if n <= 1:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
print(fibonacci(10)) # 第一次调用,计算过程
print(fibonacci(10)) # 第二次调用,直接从缓存中获取结果
4.参考资料
青训营笔记之Cion 勒索病毒的最大收益 | 豆包MarsCode AI刷题问题描述 小S在处理一个安全事故时发现,Ci - 掘金
AI刷题中档题 Cion 勒索病毒的最大收益 解答 | 豆包MarsCode AI刷题Cion 勒索病毒的最大收益 问题 - 掘金