非整除序列

题目大意及模型转换

一张由10^17个结点组成的树,2是根节点。编号i的父亲为j,其中满足i是1~j-1中任意一个数的倍数,但不是j的倍数。询问区间A~B,算出其中每个节点到根节点的距离和。


一种思路:

发现如果i满足父亲节点为j,那么i一定是g[j-1]的倍数,而不是g[j]的倍数。其中g[i]=lcm(1..i)。那么预处理出g数组,并得到结论:大于41的数,父亲一定小于等于41。那么好办了,可以暴力求出1~41中每个数的f值(f[i]表示i到根节点的距离),那么如果i的父亲为j,f[i]就为f[j]+1。


继续想下去:

现在,考虑枚举一个i,然后找出区间A~B有多少数的根节点为i。也就是说,要找出区间中有多少个数是g[i-1]的倍数却不是g[i]的倍数。这个不好搞,因为有两个约束。1、是g[i-1]的倍数。2、不是g[i]的倍数。我们发现g[i]一定是g[i-1]的倍数,那么不是g[i-1]的倍数,一定不是g[i]的倍数。那么同时满足两个约束的,就是不是g[i]的倍数的个数减去不是g[i-1]的倍数的个数。完全可以用h[i]表示枚举到i是找出的区间中有多少个数是g[i-1]的倍数却不是g[i]的倍数,h[i]=tmp-h[i-1]。tmp为不是g[i]的倍数的个数。


计算个数:

对于区间A~B,要找不是g[i]的倍数有多少个。我们先找出一对L~R,其中L是g[i]的倍数且L>=A,R是g[i]的倍数且R<=B。找不出满足条件的L,R,那么个数一定是B-A+1。否则,个数是B-R+L-A+(R-L)/g[i]*(g[i]-1)。易证,每g[i]个就有g[i-1]个不是g[i]的倍数,然后再统计两边余下部分。

<think>嗯,用户的问题是关于如何计算或生成被3整除的子序列的算法或实现方法。我需要先回忆一下相关的动态规划方法,可能涉及到模3的状态。首先,子序列问题通常可以用动态规划来解决,尤其是当涉及到特定条件如整除性的时候。被3整除的子序列,应该考虑每个数字对3取模后的余数,这样可以将问题转化为处理余数的组合。然后,动态规划的状态设计是关键。可能的状态应该是dp[i][j],表示前i个数字中,余数为j的子序列的数量。这里的j可以是0、1、2,分别对应余数为0、1、2的情况。接下来是状态转移方程。对于每个新加入的数字,它的余数num会影响之前的状态。比如,当前数字的余数是num,那么对于每一个可能的余数j,新的余数就是(j +num)%3。因此,状态转移可能涉及到将之前的余数j与新余数num相加后的模3结果。另外,还需要考虑单独选择当前数字作为一个子序列的情况,这时候需要将dp[i][num]加1。这一步可能容易遗漏,需要特别注意。然后,代码实现方面,用户提供的引用[1]中的例子可能有用。那里的代码处理了字符串中的每个字符,将其转换为数字后对3取模,然后用动态规划数组来记录状态。需要检查代码中的循环结构和状态转移是否正确。 例如,代码中的循环遍历每个字符,然后对于每个余数j,更新dp[i][j]的值。这里可能用到了前一个状态的值,以及考虑当前数字加入后的余数变化。状态转移方程应该是dp[i][j]= dp[i-1][j]+ dp[i-1][(j- num+3)%3],这里可能是在处理余数的组合,比如之前的余数是(j- num) %3,加上当前num后余数变成j。不过这里可能需要仔细推导一下,比如如果之前有一个子序列的余数是k,那么加上当前数字num后的余数就是(k+ num) %3。因此,要找到所有k,使得(k +num)%3等于目标余数j。这时候k应该等于 (j -num)%3。因此,dp[i][j]应该加上dp[i-1][(j -num)%3]。这可能就是代码中的逻辑。 同时,单独处理当前数字的情况,也就是将dp[i][num]加1,因为当前数字本身就是一个子序列,余数就是num。 最后,初始化的问题,初始状态应该是dp[0][0]=0,其他可能都是0,然后逐步更新。在用户提供的代码中,循环从1到len,每次处理一个字符,计算其num,然后对每个j进行状态转移,最后单独处理当前数字的情况。最后输出dp[len][0],也就是整个字符串处理后余数为0的子序列数量。 可能需要注意的问题包括数组的索引是否正确,比如从1开始还是0开始,以及模运算的正确处理,避免负数的情况。比如,在代码中使用(j -num +3)%3来确保结果负。 此外,用户提到的另一个引用[2]是关于最大整除子集的问题,但可能和当前问题无关,可以暂时忽略。 总结来说,算法的大致步骤是:1.将每个数字转换为对3取模的余数。 2.使用动态规划数组dp[i][j]记录前i个数字中余数为j的子序列数量。 3.状态转移时,考虑是否包含当前数字,更新对应的余数状态。4.单独处理当前数字作为子序列的情况。5.最终结果存储在dp[len][0]中,即所有余数为0的子序列数量。可能还需要考虑子序列的数量可能常大,需要取模,如代码中的mod变量。 现在需要将这些步骤用中文清晰地解释给用户,并确保代码部分正确,语法正确,符合用户的要求。</think>### 被3整除的子序列算法解析 #### 核心思路 通过动态规划(DP)记录**前i个字符构成的子序列**对3取模后的余数分布。状态定义为$dp[i][j]$,表示前$i$个字符中余数为$j$的子序列数量($j \in \{0,1,2\}$)[^1]。 #### 算法步骤 1. **预处理字符**:将每个字符转换为数字后对3取模,得到余数$num$。 2. **状态转移**: - **继承前i-1位的状态**:不选当前字符时,余数分布不变。 - **组合前i-1位的状态**:若前i-1位存在余数为$k$的子序列,加上当前字符后余数变为$(k+num)\%3$。 - **单独选当前字符**:当前字符自身构成一个子序列,余数为$num$。 3. **转移方程**: $$ dp[i][j] = (dp[i-1][j] + dp[i-1][(j-num+3)\%3]) \% mod $$ 随后单独处理当前字符的贡献: $$ dp[i][num] = (dp[i][num] + 1) \% mod $$ #### 代码实现 ```python MOD = 10**9 + 7 def count_divisible_subsequences(s: str) -> int: n = len(s) dp = [[0] * 3 for _ in range(n+1)] for i in range(1, n+1): num = int(s[i-1]) % 3 # 字符转为数字并取模 for j in range(3): # 继承前i-1位的状态 + 组合前i-1位的状态 dp[i][j] = (dp[i-1][j] + dp[i-1][(j - num + 3) % 3]) % MOD # 单独选当前字符 dp[i][num] = (dp[i][num] + 1) % MOD return dp[n][0] ``` #### 关键点解释 - **状态转移逻辑**:通过$(j - num + 3)\%3$反向推导出前i-1位需要满足的余数,确保数学正确性。 - **单独处理当前字符**:每个字符本身就是一个子序列,需单独累加到对应余数状态中[^1]。 - **时间复杂度**:$O(n)$,空间复杂度可优化至$O(1)$(仅需记录前一位的状态)。 ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值