困难难度的一道题,挺有意思
问题描述
小R有一排长度为 n
的格子,每个格子从左到右编号为 1 到 n。起初,部分格子已经被染成了红色,其他格子则没有颜色。红色格子的状态由一个长度为 n 的字符串 s
描述,其中 s[i] = 1
表示第 i
个格子是红色的,而 s[i] = 0
表示该格子没有颜色。
小R希望通过以下两种操作将所有格子都染成红色:
- 如果第
i
个格子是红色的,且i + 1 ≤ n
,则可以将第i + 1
个没有颜色的格子染成红色。 - 如果第
i
个格子是红色的,且i - 1 ≥ 1
,则可以将第i - 1
个没有颜色的格子染成红色。
请你帮小R计算出,存在多少种不同的染色顺序可以使所有格子最终都被染成红色,并输出答案对 10^9 + 7
取模后的结果。
思路:BFS + 状态压缩动态规划
为了高效解决这个问题,我们采用 BFS(广度优先搜索) 与 状态压缩动态规划 的结合。
- BFS:用来遍历所有可能的染色状态,确保染色顺序依次进行,避免重复计算。
- 状态压缩:使用整数来表示每个格子的染色情况,以减少内存消耗和提高效率。
状态压缩解释
- 每个格子的状态用二进制位表示:
1
表示红色,0
表示无颜色。 - 一个整数 mmm 可以用来表示整个格子的状态。例如,状态
01101
对应的整数是 131313(二进制转换为十进制)。 - 状态压缩使得我们能够用位运算快速判断和更新格子的状态。
具体实现
- 初始状态:将字符串 sss 转换为整数形式 ststst,表示初始状态。
- 目标状态:所有格子都染成红色,可以表示为 (1≪n)−1(1 \ll n) - 1(1≪n)−1。
- DP 数组:
dp[m]
表示状态 mmm 下的染色顺序数量。 - 队列:使用 BFS 来遍历所有可能的状态。
代码实现
def solution(n: int, s: str) -> int:
st = int(s, 2)
m = (1 << n) - 1
dp = [0] * (m + 10)
vis = [0] * (m + 10)
dp[st] = 1
q = []
q.append(st)
while q:
m = q.pop(0)
if vis[m] == 1:
continue
vis[m] = 1
for i in range(n):
if m & (1 << i):
continue
if (i - 1 >= 0 and (m & (1 << (i - 1))) or (i + 1 < n and (m & (1 << (i + 1))))) :
nxt = m | (1 << i)
dp[nxt] += dp[m]
q.append(nxt)
return dp[m]
代码解析
-
初始状态设置:
- 将字符串 sss 转换为二进制整数
st
,表示初始的染色状态。 - 目标状态
final_state
为 (1≪n)−1(1 \ll n) - 1(1≪n)−1,即所有格子都被染成红色的状态。
- 将字符串 sss 转换为二进制整数
-
BFS 遍历:
- 使用队列
q
来存储待处理的状态,从初始状态st
开始进行广度优先搜索。 - 每次取出一个状态,尝试将每个未染色的格子染成红色。
- 如果某个格子可以被染色,则计算新的状态
next_state
,并将其加入队列。
- 使用队列
-
动态规划更新:
dp[current_state]
表示当前状态下的染色顺序数量。- 如果可以染色,则将
dp[next_state]
更新为(dp[next_state] + dp[current_state]) % MOD
,防止数值溢出。
-
最终结果:
- 返回
dp[final_state]
,即所有格子都被染成红色的染色顺序数量。
- 返回
复杂度分析
- 时间复杂度:每个状态最多遍历一次,状态总数为 2n2^n2n,因此时间复杂度为 O(2n×n)O(2^n \times n)O(2n×n)。
- 空间复杂度:需要 O(2n)O(2^n)O(2n) 的空间来存储 DP 数组和访问标记数组。
思路总结
- 状态压缩:通过将格子的染色情况压缩成一个整数,大大减少了空间消耗。
- BFS:广度优先搜索确保遍历所有可能的染色顺序,且不会重复访问状态。
- 动态规划:通过 DP 数组记录每个状态的染色顺序数量,避免重复计算。