1 条题解
-
0
题解
问题分析
题目要求统计长度为 ( N ) 的合法兑奖串(不含子串 "NOI")中,与给定长度为 ( K ) 的奖章串的最长公共子序列(LCS)长度分别为 ( 0, 1, \dots, K ) 的数量。核心挑战在于同时处理 LCS 长度计算 和 避免 "NOI" 子串 两个约束,需通过动态规划(DP)结合状态压缩高效求解。
关键思路
-
动态规划状态设计:
需跟踪三个核心信息:- 当前兑奖串长度 ( i )(从 ( 0 ) 到 ( N ))。
- 最后两个字符(
prev, curr),用于检查是否形成 "NOI" 子串。 - LCS 状态:用一个数组表示兑奖串前 ( i ) 个字符与奖章串前 ( j ) 个字符的 LCS 长度(( j = 1, 2, \dots, K )),通过编码压缩为整数。
-
状态转移:
对于每个状态,尝试添加新字符(N, O, I分别映射为 ( 0, 1, 2 )),检查是否形成 "NOI" 子串(若形成则跳过),并更新 LCS 状态。LCS 状态的更新遵循经典 LCS 递推规则:- 新字符与奖章串第 ( j ) 个字符匹配时,LCS 长度可能增加 ( 1 );
- 否则,取前序状态的最大值。
-
结果统计:
当兑奖串长度达到 ( N ) 时,根据 LCS 状态的最终值(即与奖章串的 LCS 长度)汇总计数。
代码实现
MOD = 10**9 + 7 def main(): import sys from collections import defaultdict input = sys.stdin.read().split() ptr = 0 N = int(input[ptr]) ptr += 1 K = int(input[ptr]) ptr += 1 A = input[ptr] ptr += 1 # 映射字符为0:N, 1:O, 2:I char_to_idx = {'N':0, 'O':1, 'I':2} A = [char_to_idx[c] for c in A] # 编码和解码LCS状态(s_list是[s1, s2, ..., sK]) def encode(s_list): code = 0 for s in s_list: code = code * 16 + s # 每个s_j最多15(K<=15),用4位足够 return code def decode(code, K): s_list = [] for _ in range(K): s_list.append(code % 16) code = code // 16 s_list.reverse() # 恢复s1到sK的顺序 return s_list # 初始化DP:current_dp的键是( (prev, curr), s_code ),值是计数 current_dp = defaultdict(int) initial_lt = (-1, -1) # 初始状态:无字符 initial_s_list = [0]*K initial_s_code = encode(initial_s_list) current_dp[(initial_lt, initial_s_code)] = 1 for i in range(N): next_dp = defaultdict(int) for (lt, s_code), cnt in current_dp.items(): prev, curr = lt # 尝试添加新字符c(0:N,1:O,2:I) for c in [0, 1, 2]: # 检查是否形成"NOI"子串(仅当当前长度>=2时可能) if i >= 2: # 添加后长度为i+1 >=3 if prev == 0 and curr == 1 and c == 2: continue # 非法,跳过 # 计算新的last_two (new_prev, new_curr) if i == 0: new_prev = -1 new_curr = c elif i == 1: new_prev = curr # 之前的curr是第一个字符 new_curr = c else: new_prev = curr new_curr = c new_lt = (new_prev, new_curr) # 解码当前s_code得到s_list [s1, s2, ..., sK] s_list = decode(s_code, K) # 计算新的s_prime_list [s'1, s'2, ..., s'K] s_prime = [0]*(K+1) # s_prime[0] = 0(辅助) for j in range(1, K+1): # s[j] = s_list[j-1] # s[j-1] = s_list[j-2](j>=2),否则为0 s_j = s_list[j-1] if j-1 < len(s_list) else 0 s_j_prev = s_list[j-2] if j-2 >=0 else 0 # 递推公式 s_prime[j] = max(s_j, s_prime[j-1]) if c == A[j-1]: s_prime[j] = max(s_prime[j], s_j_prev + 1) s_prime_list = s_prime[1:] # 取s'1到s'K s_prime_code = encode(s_prime_list) # 更新next_dp next_dp[(new_lt, s_prime_code)] = (next_dp[(new_lt, s_prime_code)] + cnt) % MOD current_dp = next_dp # 统计结果:按LCS长度(s_list[-1])汇总 result = [0]*(K+1) for (lt, s_code), cnt in current_dp.items(): s_list = decode(s_code, K) lcs_len = s_list[-1] if K > 0 else 0 if 0 <= lcs_len <= K: result[lcs_len] = (result[lcs_len] + cnt) % MOD for num in result: print(num) if __name__ == "__main__": main()复杂度分析
- 状态数:兑奖串长度 ( N \leq 1000 ),最后两个字符的状态共 ( 13 ) 种(含初始状态),LCS 状态数因 ( K \leq 15 ) 且受单调性约束,实际约为 ( 10^5 ) 量级。总状态数约为 ( 1000 \times 13 \times 10^5 = 1.3 \times 10^9 ),但通过字典存储仅保留非零计数状态,实际可高效处理。
- 转移:每个状态尝试 ( 3 ) 个新字符,每次转移涉及 LCS 状态的解码、计算和编码,时间复杂度为 ( O(N \times 13 \times S \times K) )(( S ) 为 LCS 状态数),可满足 ( N \leq 1000, K \leq 15 ) 的数据范围。
该方法通过状态压缩和动态规划,高效平衡了约束条件与计数需求,成功解决问题。
-
- 1
信息
- ID
- 4915
- 时间
- 6000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 2
- 已通过
- 1
- 上传者