第 2 题
题意分析
给定一个长度为 NNN 的数列 AAA,以及整数 KKK。盘子中初始有 000 颗糖,进行 KKK 次操作:
* 设当前盘子有 XXX 颗糖,计算下标 i=X mod Ni = X \bmod Ni=XmodN
* 向盘子中加入 AiA_iAi 颗糖(即 X←X+AiX \leftarrow X + A_iX←X+Ai )
求 KKK 次操作后盘子中糖的总颗数。
关键约束:
* 2≤N≤2×1052 \le N \le 2 \times 10^52≤N≤2×105(数组长度,决定状态空间)
* 1≤K≤10121 \le K \le 10^{12}1≤K≤1012(操作次数极大,不能直接模拟 KKK 次)
* 1≤Ai≤1061 \le A_i \le 10^61≤Ai ≤106
注意到每次操作只依赖于 X mod NX \bmod NXmodN,而 X mod NX \bmod NXmodN 的取值范围是 [0,N−1][0, N-1][0,N−1],最多只有 NNN 种不同状态。根据鸽巢原理,最多模拟 N+1N+1N+1 次操作就一定会出现重复的状态(即相同的余数)。一旦余数重复,由于后续操作完全由当前余数决定,整个数列的状态转移就进入了循环节。因此我们可以用 O(N)O(N)O(N) 的时间找到循环,再用 O(1)O(1)O(1) 的时间加速计算后续巨量的重复操作。
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
思路分析
核心:记录每个余数第一次出现的时机
我们维护以下信息:
* XXX:当前盘子中的糖数(即题目中的总颗数)
* step:已经执行完成的操作次数
* seen[r]:记录余数 rrr 第一次出现时,(step, X) 的数值对
模拟过程:
1. 计算当前余数 r=X mod Nr = X \bmod Nr=XmodN
2. 如果 seen[r] 已经有记录,说明循环节找到了:
* 设上次出现余数 rrr 时,操作次数为 pre_step,糖数为 pre_val
* 当前是第 step 次操作前,糖数为 XXX
* 这意味着从 pre_step 到 step 之间的 cycle_len = step - pre_step 次操作构成一个完整循环
* 这个循环内糖数增加了 cycle_sum = X - pre_val
* 剩余操作次数 remain = K - pre_step,我们还可以执行 remain / cycle_len 个完整循环,以及 remain % cycle_len 次零散操作
* 直接跳到循环结束(或散段结束),不再逐项模拟
3. 如果 seen[r] 没有记录,说明是新状态:
* 记录 seen[r] = (step, X)
* 执行一次操作:XleftarrowX+A[r]X leftarrow X + A[r]XleftarrowX+A[r],step++
注意:题目中 KKK 次操作的含义是"进行 KKK 次加法"。我们在循环检测时,检测到余数重复意味着下一次要加的数和之前某次完全一样,且当时的总糖数状态也完全一样,所以后续行为必然重复。
循环计算公式
设循环前前缀有 pre_step 次操作,对应糖数 pre_val。
进入循环后,还剩余 remain = K - pre_step 次操作需要执行。
* 完整循环个数:loops = remain / cycle_len
* 完整循环增加的糖数:loops * cycle_sum
* 散段长度:rest = remain % cycle_len
散段需要模拟吗?我们可以预先记录散段中每一步的操作序列,但实际上:
如果我们已经走到了当前 XXX,且知道从 pre_step 到 pre_step + rest 这 rest 步怎么走的……
等等,这里有个技巧:我们在检测到循环时,已经执行完了 step 次操作,当前糖数是 XXX。 检测到循环意味着"如果现在继续操作,将会和历史上某一段完全重复"。
更好的写法:我们在执行操作前检查当前余数是否出现过。
* 若未出现,记录当前 (step, X),然后执行操作
* 若已出现,说明从上次记录点到当前点之间是一个循环
这样更清晰:
设上次记录余数 rrr 时:
* 已执行 pre_step 次操作
* 当时糖数为 pre_val
当前状态:
* 已执行 step 次操作
* 当前糖数为 XXX
则从 pre_step 次操作后到 step 次操作前,中间经历了 cycle_len = step - pre_step 次操作,糖数从 pre_val 增加到 XXX,增加了 cycle_sum = X - pre_val。
此时还剩 remain = K - step 次操作未执行(注意!是当前状态之后还剩这么多)。
我们可以:
1. 跳过 loops = remain / cycle_len 个完整循环:X+=loopstimescyclesumX += loops times cycle_sumX+=loopstimescycles um,step += loops \times cycle_len
2. 然后还剩 rest = remain % cycle_len 次操作,因为最多 NNN 次,直接模拟即可
复杂度
* 时间:O(N)O(N)O(N),最多遍历 NNN 个不同余数
* 空间:O(N)O(N)O(N)
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
代码实现(逐行解释)