Deepseek R1 用 595 秒基本战胜了 SA 毒瘤题 P9396
结局
https://www.luogu.com.cn/record/202208276
推理过程
好的,我现在得仔细看看这道题。题目是说,每个m的立方可以表示为m个连续的奇数的和。例如,给出的例子中,3³等于7+9+11,这三个连续奇数。然后,对于给定的奇数x,f(x)就是这个m的值,使得当m³分解成这些连续奇数时,x是其中的一个数。比如,x=21的时候,出现在m=5的序列里,所以f(21)=5。
现在的问题是要计算s的值,s是所有从i=1到(k+1)/2的f(2i-1)之和,也就是所有奇数1,3,5,...,k对应的f值的总和模1e9+7。并且,k的可能非常大,达到2^64-1,而且有多达3e6次的测试用例。所以必须找到一个非常高效的算法,否则会超时。
首先,我得理解每个奇数x对应的f(x)是如何确定的。假设我们有某个奇数x,我们需要找到最大的m,使得x属于将m³分解成m个连续奇数的那个序列中。那这个序列的构造方式有什么规律吗?
观察给出的例子:
m=1:1 → 和为1³,只有一个数。
m=2:3+5 → 和是8=2³。
m=3:7+9+11 → 和为27=3³。
m=4:13+15+17+19 → 和为64=4³。
m=5:21+23+25+27+29 → 和为125=5³。
看起来,对于每个m来说,这个连续奇数的起始数是怎样的?我们可以找出起始数的规律。例如:
m=1,起始是1。
m=2,起始是3=1+2。
m=3,起始是7=3+4。
m=4,起始是13=7+6。
m=5,起始是21=13+8。
所以,起始数的增量每次增加2。比如,m=1到m=2,起始数增加了2;m=2到3,增了4;m=3到4,增了6;m=4到5,增了8。这似乎形成一个等差数列,每次增量是2(m-1)。或者,可能起始数的通项公式是什么?
或者,我们可以试着找出每个m对应的起始数的数学表达式。比如,观察起始数的序列:
m | 起始数
1 | 1
2 | 3
3 | 7
4 |13
5 |21
这里,每个起始数之间的差是2,4,6,8,也就是每次差增加2。那么,起始数的通项公式可能是什么呢?
起始数构成的数列是1,3,7,13,21...。观察这些数之间的差:
3-1=2;7-3=4;13-7=6;21-13=8。所以,第m项的起始数可以表示为1 + 2 +4 +6 + ... + 2(m-1-1)。或者说,起始数的通项公式是m(m-1)+1?比如,当m=1时,1(0)+1=1;m=2时,21+1=3;m=3时,32+1=7;m=4时,43+1=13;m=5时54+1=21。这似乎是对的。所以起始数的公式是start = m(m-1)+1。
那么,每个m对应的连续奇数是start, start+2, ..., start+2(m-1)。例如,对于m=3,start是7,三个数是7、9、11,每个间隔2。那么,这些奇数的总和等于m³,对吧?而每个连续的m个奇数的和是m³吗?应该是对的,因为题目中的例子都是这样的。
现在,对于给定的奇数x,我们要找到最大的m,使得x属于某个m对应的序列中的某一个奇数。也就是说,x必须出现在某个m的连续奇数序列中。那么,如何根据x找到对应的m呢?
假设x属于m的序列中的某一个数,那么x必须满足start ≤x ≤start +2(m-1),其中start=m(m-1)+1。并且,start是第m个序列的起始奇数。例如,当x是21时,属于m=5的序列,起始是21,所以该序列的数是21,23,25,27,29。所以,如果x等于start +2k,其中k是0到m-1之间的整数,那么m就是对应的f(x)。
所以,我们的目标是找到最大的m,使得存在k(0≤k<m),使得x= m(m-1)+1 +2k。或者说,可以重写为x = m² -m +1 +2k。其中k的取值范围是0 ≤k <m → 0 ≤2k <2m → x的取值范围是 m² -m +1 ≤x ≤m² -m +1 +2(m-1) → 也就是 m² -m +1 ≤x ≤m² +m -1。例如,当m=5时,x的范围是21~29之间的奇数。比如x=21属于m=5,而x=23也属于m=5,依此类推直到29。
所以,给定x,要找到最大的m,使得m² -m +1 ≤x ≤m² +m -1。并且,这个区间内的x是连续的奇数,所以中间的每个奇数x都对应唯一的m吗?
假设是的。那么,我们需要找到m的最大值满足这个不等式。这时候,我们可以将不等式转化为求解关于m的二次方程或者不等式。
首先,x是已知的,那么对于x来说,满足m² -m +1 ≤x ≤m² +m -1。我们需要找出最大的m满足这个条件。或者,可以解这个不等式,找到可能的m的范围。
例如,对于m来说,左边的式子m² -m +1 ≤x → m² -m ≤x-1 → m² -m -(x-1) ≤0。右边是x ≤m² +m -1 → m² +m -1 -x ≥0。
或者,我们可以将左右的两个不等式结合起来。比如,左边是m² -m ≤x-1,右边是m² +m ≥x+1。这可能比较复杂。或许,可以将不等式两边合并成一个关于m的表达式。比如,x应该处于m² -m +1到m² +m -1之间。那这个区间的长度是(m² +m -1) - (m² -m +1) )=2m-2。这个区间内的所有奇数,都对应同一个m吗?
例如,当m=5时,区间是21到29,共有5个数,每个数对应m=5。这似乎是对的。例如,对于m=3,区间是7到11,三个数。所以,每个m对应的区间包含m个连续的奇数,从起始数开始,步长为2。
那,对于给定的x,我们需要找到最大的m,使得x位于该m的区间内。或者,是否存在多个m满足条件?比如,一个x可能属于多个不同的m的区间吗?
例如,假设x=5。看看各个m的情况:
m=2的区间是3+5 → 3到5。所以x=5属于m=2的区间。而m=3的区间是7~11,不包括5。所以x=5对应的m=2。那是否存在某个x属于多个m的情况?
比如,假设有一个x=7。m=3的起始是7,区间是7~11。那x=7只能属于m=3的区间吗?或者有没有可能,某个更大的m的区间包含它?
比如,m=4的起始是13,所以区间是13~19。所以x=7不在其中。所以,每个奇数x只能属于唯一的一个m的区间。那么,每个x对应的f(x)是唯一的。所以,对于给定的x,只需要找到对应的m即可。
那么,如何将x转换为对应的m呢?
假设我们有一个x,要找到最大的m,使得m² -m +1 ≤x ≤m² +m -1。这可能可以通过求解m的二次方程来找到可能的范围。
例如,考虑x的位置,我们可以将不等式重写为:
m² -m +1 ≤x ≤m² +m -1.
我们可以将这两个不等式结合起来,得到:
m² -m ≤x-1 → m² -m - (x-1) ≤0.
右边是x ≤m² +m -1 → m² +m -1 ≥x → m² +m -x -1 ≥0.
这似乎很难直接解。但或许,可以将整个式子变形,找出m的表达式。例如,将整个式子视为关于m的一元二次不等式,求解m的可能范围。
比如,对于m² -m +1 ≤x → m² -m + (1 -x) ≤0.
这个方程的解是m在 [ (1 - sqrt(4x-3))/2 , (1 + sqrt(4x-3))/2 ] 之间。但这个时候,可能还需要考虑另一个不等式x ≤m² +m -1。
或者,我们可以将两个不等式结合起来:
m² -m +1 ≤x ≤m² +m -1.
考虑整个式子,可以将两边都变形:
左边:m² -m +1 ≤x → m² -m ≤x-1 → m² -m + (1/4) ≤x-1 + (1/4) → (m-0.5)^2 ≤x -0.75 → m-0.5 ≤sqrt(x -0.75) → m ≤sqrt(x -0.75) +0.5.
右边:x ≤m² +m -1 → m² +m -x -1 ≥0 → 解这个不等式的话,判别式是1 +4(x+1) =4x+5. 所以根为 [-1 ±√(4x+5)]/2. 由于m是正整数,所以考虑正根:m ≥ [ -1 +√(4x+5) ] / 2.
所以,结合起来,m的范围是 [ [ (-1 +√(4x+5) ) / 2 ] , sqrt(x-0.75)+0.5 ] 之间。因为m必须是整数,所以可能的m值在这个区间内的整数。并且,我们要找最大的m满足条件。
例如,假设x=21。计算:
左边:sqrt(21-0.75) =sqrt(20.25)=4.5 → 4.5+0.5=5 → m ≤5.
右边:计算4x+5=4*21+5=89 → sqrt(89)≈9.433 → (-1+9.433)/2≈4.216 → 所以m≥5(因为要上取整吗?或者可能我的推导有问题)。这个时候,右边的表达式给出的可能范围是m≥某个值。例如,当x=21时,右边的解是m≥ ( -1 + sqrt(89) )/2 ≈4.216。所以,m的可能整数值是5,4.216<=m <=5 → 最大的是5。这样,代入x=21到不等式:
m² -m +1 =5*4 +1=21 ≤21 → 成立。右边是5²+5-1=29 ≥21 → 成立。所以,m=5是符合条件的。这样,f(21)=5。
这似乎是对的。那对于x来说,m的取值是等于floor( (sqrt(4x+1)+1)/2 )?或者需要找到一个更简单的方式?
另一个思路:对于给定的x,观察m的范围。比如,假设x在某个m对应的区间中,那么这个区间的起始是m(m-1)+1,终止是m(m-1)+1+2(m-1) =m^2 -m +1 +2m-2= m² +m -1. 所以,区间是[m(m-1)+1, m² +m -1]。这个区间的长度是2m-2+1=2m-1?或者计算终止和起始之差:m² +m -1 - (m² -m +1)=2m-2。所以,区间内的元素数目是m个,每个间隔2,所以总共有m项。例如,m=3时,区间是7到11,三个数:7、9、11。
所以,每个m对应的区间长度是2m-2,而里面的奇数的数量是m个。所以,区间内的奇数数目是m个,每个步长是2。因此,该区间中的每个奇数x满足:x = m(m-1)+1 + 2k,其中k=0,…,m-1.
现在,我们想找到最大的m,使得x在该区间内。那如何求这个m?
假设x是某个奇数,那么m的最大可能值由x的位置决定。例如,当x=1时,m=1;x=3是m=2;x=5是m=2;x=7是m=3;x=9是m=3;x=11是m=3;等等。
现在,假设我们想找出x对应的m,那么可以推导出:
对于x来说,存在m满足:
m(m-1)+1 ≤x ≤m(m+1)-1.
这可能吗?例如,当m=5时,54+1=21,56-1=29,所以x的取值范围是21到29,确实符合之前的例子。
那这个式子是否可以合并成:
m(m-1)+1 ≤x ≤m(m+1)-1 → m² -m +1 ≤x ≤m² +m -1.
是的。那现在的问题是如何解这个不等式,找到最大的m满足条件。
我们可以将不等式看作:
m² -m +1 ≤x ≤m² +m -1.
现在,要解这个不等式,找到满足条件的m的最大值。
我们可以将这两个不等式分别处理:
左边的不等式m² -m +1 ≤x → m² -m ≤x-1 → m² -m -x +1 ≤0 → 解这个二次方程m² -m + (1 -x) =0的根。根为 [1 ±√(1-4*(1-x))]/2 = [1 ±√(4x-3)]/2。因为x是奇数且至少是1,所以4x-3≥1,根是实数。对于m来说,m必须为正整数,所以取较大的根:[1+√(4x-3)]/2。所以左边的解是m ≤ [1+√(4x-3)]/2。
右边的不等式x ≤m² +m -1 → m² +m -1 ≥x → m² +m -(x+1) ≥0. 解这个二次方程m² +m -x-1=0的根是 [-1 ±√(1+4(x+1))]/2 = [-1 ±√(4x+5)]/2。因为m是正整数,所以取正根:[ -1 +√(4x+5) ] / 2。所以右边的解是m ≥ [ -1 +√(4x+5) ] /2.
所以,满足左边和右边的条件的m必须在这两个解之间:
[ (-1 +√(4x+5))/2 ] ≤m ≤ [ (1+√(4x-3))/2 ]
因为我们需要最大的m,所以可能取这个区间的上限。或者,可能最大的m是满足该区间的整数部分。
或者,可以找到m的表达式为:对于给定的x,m的取值是满足上述两个不等式的整数,并且最大的可能值。这个时候,可以推导出m的近似表达式。
比如,当x很大时,左边的不等式给出的上限约为sqrt(x)/1,而右边的下限约为sqrt(x)/1。可能两者的平均值接近某个数。或者,可以尝试合并这两个式子。
或者,可以观察到对于给定的x,我们可以解这两个不等式,得到m的可能范围。例如,假设我们有:
m ≤ [1+sqrt(4x-3)]/2.
m ≥ [ -1 + sqrt(4x+5) ] /2.
这两个值的差是多少呢?比如,当x=21时:
sqrt(4x-3)=sqrt(84-3)=sqrt(81)=9 → [1+9]/2=5.
sqrt(4x+5)=sqrt(84+5)=sqrt(89)≈9.433 → [ -1+9.433 ]/2≈4.216 → 所以m的下限是4.216,上限是5。所以,m的整数解是5,即取最大的整数满足这两个条件。
类似地,对于x=3:
左边:sqrt(4*3-3)=sqrt(9)=3 → [1+3]/2=2.
右边:sqrt(4*3+5)=sqrt(17)≈4.123 → [ -1+4.123 ]/2≈1.561 → 所以m的下限是1.561,上限是2。所以,m=2。
这时候,这两个式子给出的区间可能刚好包含唯一的整数m。那这个时候,对于给定的x,最大的m是floor( (1+sqrt(4x-3))/2 )吗?
或者,可以找到这样的m,即取:
m = floor( (sqrt(4x+1) +1)/2 )
这可能是一个可能的表达式。例如,当x=21时,4x+1=85,sqrt(85)=约9.219,+1=10.219,除以2是5.109,取floor为5。这正确。当x=3时,4x+1=13,sqrt=3.605,+1=4.605/2=2.302,floor是2。正确。当x=5时,4x+1=21,sqrt=4.583,+1=5.583/2=2.791,floor是2。正确,因为x=5在m=2的区间里。当x=7时,4x+1=29,sqrt约5.385,+1=6.385/2=3.192,floor是3。正确。所以,这个表达式可能成立?
假设,对于给定的x,m等于floor( (sqrt(4x+1)+1)/2 )。那这时候,是否满足m的条件?
假设这个表达式是正确的,那么我们可以快速计算出m的值。那这时候,我们需要验证这个式子是否成立。
例如,x=1:4x+1=5,sqrt≈2.236,+1=3.236/2≈1.618,floor是1。正确。x=7:得到3。正确。x=9:同样得到3。正确。x=11:得到3。正确。x=13:代入得到m=4?
4x+1=53,sqrt≈7.28,+1=8.28/2=4.14 → floor是4。此时,m=4对应的区间是13~19。x=13属于这个区间。所以,是的。所以,这个公式可能正确。那这时候,对于任意x,m的值等于floor( (sqrt(4x+1)+1)/2 )。
那这个时候,问题转化为如何快速计算这个表达式的结果。但是,对于非常大的x(比如接近2^64),如何快速计算sqrt(4x+1)的值?
这里需要注意到,x是一个奇数,所以4x+1=4*(2k+1)+1=8k+5,可能不是平方数。但不管怎样,我们需要计算sqrt(4x+1)的整数部分,然后代入式子。
例如,假设x=264-1,那么4x+1=4*(264-1)+1=2^66-3。这时候,计算其平方根的整数部分可能比较复杂,但可以使用二分法或者牛顿迭代法。
不过,在C++中,处理大整数的平方根可能比较困难。因为k的范围是到264-1,所以4x+1可能大到接近4*(264) = 2^66。这时候,普通的浮点数可能无法精确表示这么大的数的平方根。所以,可能需要使用二分法来找到sqrt(4x+1)的整数部分。
或者,是否存在其他方式,能够将m的表达式转化为整数运算?
另一个思路是,将m的表达式写成:m = floor( (sqrt(4x+1) +1)/2 )。我们是否可以将其转化为另一种形式?
例如,令s = sqrt(4x+1)。则,s的整数部分是最大的整数t,满足t² ≤4x+1 <(t+1)^2。然后,m = floor( (t +1)/2 )。
假设t是奇数或者偶数,那么根据t的奇偶性,可能可以找到对应的m的值。或者,不管t的奇偶性,直接取(t+1)/2的整数部分?
例如,当t是奇数:比如t=5 → (5+1)/2=3 → m=3.
当t是偶数:t=4 → (4+1)/2=2.5 → floor是2.
所以,这似乎可以处理两种情况。那这个时候,找到t= floor( sqrt(4x+1) )是关键。
所以,问题转化为如何快速计算t= floor( sqrt(4x+1) ),然后计算m= (t+1)/2的整数部分。例如,当t是奇数或偶数,如何得到正确的m?
例如,假设x=21,4x+1=85。sqrt(85)=9.219,所以t=9。则m=(9+1)/2=5。正确。当x=3,4x+1=13→ t=3 → m= (3+1)/2=2。正确。当x=5,4x+1=21→t=4 → m=(4+1)/2=2.5 → floor是2。正确。当x=7,4x+1=29→t=5→m=3。正确。这样看来,不管t的奇偶性如何,计算m=(t+1)//2(整数除法)即可得到正确的m值。
例如,当t=9时,(9+1)/2=5。当t=4,4+1=5,5/2=2.5,整数除法取2。是的。所以,正确的m等于(t+1)//2,其中t是sqrt(4x+1)的整数部分。也就是说,m = (t +1) //2,其中t是最大的整数满足t² ≤4x+1。
这样,问题转化为如何计算t的值,然后计算m的值。接下来,如何高效地计算这个t?
对于非常大的x(比如接近2^64),直接计算4x可能会导致溢出,但题目中给出的k是unsigned,所以需要用64位无符号整数来处理。例如,在C++中,可以使用unsigned long long类型,但要注意,当x接近264-1时,4x+1可能会溢出,但题目中的k是正奇数,且k<264。当k是264-1时,4x+1=4*(264-1)+1=4*264-4+1=266-3,这显然超过了unsigned long long的范围(64位的话,最多能表示到2^64-1)。所以,这时候需要处理大整数的问题?
但是题目中的输入k是可以用unsigned long long存储的,因为k<264。那么4x+1的值当x是264-1时,会等于4(2^64-1) +1=42^64-3。这显然超过了64位的存储能力。所以,如何处理这样的问题?
这时候可能需要使用数学方法或者位运算来计算t的值,而无需显式地计算4x+1的值。或者,可以将x视为一个很大的数,比如用字符串表示,或者用某种高精度方法处理。但是这样的话,对于3e6次查询来说,这样的方式可能不够高效。
这时候,我们需要找到一个替代的方法,不直接计算4x+1的值,而通过其他方式找到t的值。
或者,是否存在某种数学规律,使得我们可以将x转换为m的值,而无需直接计算4x+1的平方根?
观察问题,当x是某个奇数时,它的m值是满足m(m-1)+1 ≤x ≤m(m+1)-1的最大m。或者,可以将这个式子重新排列:
m(m-1) ≤x-1 → m² -m -x +1 ≤0.
同时,x ≤m² +m -1 → m² +m -x -1 ≥0.
这可能比较难解。不过,我们是否可以重新整理这两个不等式?
将这两个不等式合并,得到:
m² -m +1 ≤x ≤m² +m -1.
我们希望找到最大的m满足这个条件。这可能可以通过二分法来解决。
例如,对于给定的x,可能的m的范围是1到某个上限。比如,因为x≥1,所以m的上限可能大约为sqrt(x)+1。因此,可以使用二分法在可能的m范围内寻找最大的满足条件的m。
那如何判断一个m是否满足条件?
对于某个m,我们只需要检查:
m(m-1)+1 ≤x ≤m(m+1)-1.
如果成立,那么这个m是一个可能的候选。我们的目标是找到最大的这样的m。
例如,对于x=21:
检查m=5:54+1=21 ≤21 ≤56-1=29 → 成立。
检查m=6:6*5+1=31>21 → 不成立。所以最大的m是5。
所以,二分法的步骤可以是:
初始化low=1,high=某个估计的上界(比如sqrt(x)+2)。
循环直到low <=high:
mid = (low+high)/2.
计算区间是否包含x。即,判断mid(mid-1)+1 <=x <=mid(mid+1)-1.
如果成立,则记录mid,并尝试更大的值,即low=mid+1.
否则,如果x >mid*(mid+1)-1,说明需要更大的m,所以low=mid+1.
如果x <mid*(mid-1)+1,说明需要更小的m,所以high=mid-1.
最后,最大的m就是找到的满足条件的最大值。
这可能是一个可行的方法,但问题在于当x很大时,mid(mid+1)可能会溢出,尤其是当mid是接近232的时候,mid*(mid+1)会达到264,可能超出64位无符号整数的范围。例如,当mid=232时,mid*(mid+1)=232(232+1)≈264+2^32,这超过了unsigned long long的范围,导致溢出。所以,这样的计算在C++中可能导致错误的结果。
因此,直接使用二分法可能会有溢出的问题。所以需要找到一个避免计算mid*(mid+1)的方法,或者找到一个方式,在计算的时候判断是否溢出。
或者,可以将不等式转换为比较x与mid²的形式。例如:
m(m-1)+1 ≤x → m² -m +1 ≤x → m² ≤x +m -1 → m² ≤x +m -1.
而右边的不等式x ≤m(m+1)-1 → x+1 ≤m² +m → x+1 ≤m(m+1).
这样,可能可以将比较转化为更不容易溢出的形式?
或者,我们可以将这两个不等式转化为:
m² -m +1 ≤x → m² ≤x +m -1 → m <= sqrt(x +m -1).
这可能没有帮助。
或者,当处理m较大的情况时,例如,当m2超过x的值时,可以判断mid是否可能满足条件。例如,当mid2 >x + mid -1时,则说明mid太大,需要减小high。
这可能比较复杂。
那么,这时候,如何高效地计算m的值呢?
回到之前的公式,假设m的表达式是floor( (sqrt(4x+1)+1)/2 )。所以,关键在于如何计算sqrt(4x+1)的整数部分。这可能需要使用到平方根的计算方法,特别是对于非常大的x,比如当x接近264-1时,4x+1=4*(264-1)+1=2^66-3,这明显超出了64位无符号整数的范围。因此,无法直接计算4x+1的值。
这时候,我们需要找到一种方法来计算sqrt(4x+1)的整数部分,而无需显式地计算4x+1的值。或者,是否可以将x的平方根转化为其他形式?
例如,sqrt(4x+1) = 2*sqrt(x) + ... ? 但可能没有这样的简单关系式。
另一种思路:假设x很大,那么4x+1≈4x,所以sqrt(4x+1)≈2*sqrt(x). 但可能这并没有直接的帮助。
或许,我们可以用位运算或二分法来计算sqrt(4x+1)的整数部分。例如,找到最大的t,使得t² ≤4x+1.
这可能需要一个高效的平方根算法,适用于非常大的整数,比如使用逐位确定的方法。
但考虑到x的范围是到264-1,所以4x+1的范围是到266-3,因此,t的最大可能值是到2^33(因为 (233)2=2^66,而4x+1可能接近这个值)。
对于这样的t,我们可以用二分法来找t的最大值,使得t² ≤4x+1.
这样,即使对于最大的x来说,二分法的次数也是log2(t)次,也就是最多约34次循环,这对于3e6次查询来说,是可以接受的。
所以,这可能是一个可行的解决方案。
具体来说,对于给定的x,我们需要找到最大的t,使得t² ≤4x+1.
这可以通过二分法实现:
初始化low=0,high=2e33(或者其他足够大的值,比如x的平方根的两倍)。
然后,循环直到low <=high:
mid = (low + high)/2.
计算mid²的值,并与4x+1比较。
如果mid² <=4x+1 → 可能可以更大,所以low=mid+1.
否则 → high=mid-1.
最后,high就是最大的t满足t² ≤4x+1.
但是,当x是接近264-1时,4x+1的值是大约266,此时mid可以是233左右,所以mid2会是约2^66,这已经超过了64位无符号整数的范围。因此,在C++中,这样的计算将导致溢出,无法正确比较。
因此,必须找到一个方法,在计算mid²的时候避免溢出。
这个时候,可以注意到,当mid可能大到导致mid²溢出时,可以提前终止。例如,当mid超过某个值时,mid²一定大于4x+1,所以可以提前调整high的值。
例如,在二分法中,当计算mid * mid时,如果结果溢出,那么mid*mid肯定大于4x+1,所以此时应该将high设置为mid-1。
因此,在C++中,可以使用以下条件来判断mid是否太大:
如果 mid > sqrt(4x+1), 则mid² >4x+1.
但因为无法直接计算sqrt(4x+1),所以必须找到另一种方法。例如,可以将mid与 (4x+1)/mid比较。
或者,更简单的方法是在计算mid*mid的时候,如果发生溢出,即结果超过某个值,则视为mid² >4x+1。
或者,可以利用数学关系,比如,比较mid和 (4x+1)/mid的值。
例如,在计算mid² ≤4x+1时,可以写成mid <= (4x+1)/mid。这样,在mid非常大的情况下,右边的值可能很小,从而可以比较mid和该值的大小。
但是,当mid为0时,这种方法会出错,但我们的mid的取值范围是大于等于0的。
但如何处理这种情况呢?
例如,当mid=0时,左边是0,右边是无限大,所以成立。但这只在x=0的情况下出现,而题目中的x是正奇数,所以这种情况不存在。
因此,在循环中,当mid>0时,可以比较mid与 (4x+1)/mid的大小。例如,如果mid > (4x+1)/mid → 则mid²>4x+1 → 需要调整high=mid-1.
否则,mid²<=4x+1 → 调整low=mid+1.
这样,可以避免计算mid²的值,从而防止溢出。
但是,这需要将4x+1的值保存在某个变量中,而x可以是接近264-1的数,所以4x+1可能超过64位无符号整数的最大值。例如,当x=264-1时,4x=266-4,所以4x+1=266-3,这无法用64位无符号整数表示。因此,在C++中,如何处理这个问题?
这似乎是一个难题。因为4x+1的值可能超过64位无符号整数的最大值,所以无法直接保存。那这时候,可能需要将x表示为更大的类型,比如使用__uint128_t,但这可能依赖于编译器的支持。或者,在无法使用更大的类型时,需要找到其他方法。
假设我们可以使用__uint128_t类型来处理这些大数。因为题目中的k是unsigned long long类型,所以x的范围是1 <=x <=264-1。因此,4x+1的最大值是4*(264-1)+1=266-4+1=266-3。这需要至少67位才能存储。所以,使用__uint128_t类型可以处理这种情况。例如,在C++中,某些编译器(如GCC)支持__uint128_t类型,可以存储128位的整数。
因此,假设我们可以使用__uint128_t类型,那么可以安全地计算4x+1的值,并且在二分法中比较mid和 (4x+1)/mid的值。
因此,计算t= floor(sqrt(4x+1))的步骤如下:
-
将x转换为__uint128_t类型,计算val =4x+1.
-
使用二分法找到最大的t,使得t*t <=val.
这样,可以得到正确的t的值。
然后,m=(t+1)/2的整数部分。
例如,当x=21,val=85,t=9,m=(9+1)/2=5.
当x=3,val=13,t=3,m=2.
当x=264-1,val=4*(264-1)+1=266-3。此时,t的平方应该等于或接近这个值。例如,t=233-1 → t²= (233)2 - 2*2^33 +1=266-234+1。这可能小于val=266-3。所以,最大的t可能等于233-1。而m=(t+1)/2= (233-1+1)/2=232.
此时,验证m对应的区间是否包含x=2^64-1:
m=2^32.
start =m*(m-1)+1=232*(232-1)+1=2^64 -2^32 +1.
end= m*(m+1)-1=232*(232+1)-1=2^64 +2^32 -1.
而x=2^64-1位于start和end之间吗?
start=264-232+1 ≤x=2^64-1 ≤end=264+232-1 →是的。所以,m=2^32是正确的。
所以,这样的方法是可行的。
现在,问题转化为如何高效地实现这个二分法,使用__uint128_t类型。
在C++中,处理__uint128_t需要注意输入输出的问题,但在这个问题中,输入是unsigned long long类型的k,所以可以将x转换为__uint128_t类型。
所以,具体的步骤是:
对于给定的奇数x:
-
计算val =4x +1.
-
二分法找到最大的t,使得t*t <=val.
-
m=(t+1)/2.
这样,就得到了f(x)的值。
现在,回到原题的问题,我们需要计算s= sum_{i=1}^{(k+1)/2} f(2i-1)},也就是对所有奇数1,3,5,...,k求和他们的f值。然后输出s mod 1e9+7.
但是,k的范围非常大,达到2^64-1,并且T是3e6次测试用例。如果对于每个测试用例,都要计算每个奇数的f值然后累加的话,时间复杂度是O(T * (k+1)/2),这显然无法承受。例如,当k=1e18时,每个测试用例需要1e18次计算,完全无法处理。
所以,必须找到一个数学规律,可以快速计算这个总和s的值,而不需要逐个计算每个奇数的f值。
这说明,我们需要找到一种方法,将整个求和转换为某个数学表达式或者分段的累加,从而在O(1)或者O(logk)的时间内计算每个k对应的s的值。
现在,我们需要找到,对于所有奇数x=1,3,5,...,k,它们的f(x)的值的总和。
根据之前的分析,每个x对应的m是floor( (sqrt(4x+1)+1)/2 ). 也就是,m的值对应不同的区间。比如,当x在某个区间时,对应的m是某个固定值。例如,当x在m的区间[m(m-1)+1, m(m+1)-1]中的奇数时,它们的f(x)=m.
因此,我们需要统计每个m对应的区间中的奇数的个数,然后乘以m,最后累加所有这样的贡献。
例如,对于某个m,对应的奇数的x的范围是start=m(m-1)+1到end=m(m+1)-1,且x必须为奇数。那么,这个区间内有多少个奇数?
start的奇偶性取决于m(m-1)+1。因为m(m-1)是两个连续整数相乘,所以必为偶数。所以,start=even+1=奇数。同样,end=m(m+1)-1. m和m+1的乘积是偶数,所以even-1=奇数。所以,区间[start, end]中的数都是奇数吗?是的。因为start是奇数,end是奇数,步长为2。例如,当m=2时,start=3,end=5,共有2个数:3、5。每个m对应的区间中的奇数数目是m个,因为每个m的区间是连续的m个奇数。
例如,m=2对应的区间是3、5,两个数,数目为2,等于m。m=3对应的7、9、11,三个数,等于m。所以,每个m对应的区间中正好有m个奇数,且这些奇数的f(x)=m。
所以,所有奇数的x被划分到不同的m对应的区间中,每个区间中的x的数目是m个,且每个这样的x的f(x)=m。所以,所有奇数的总和可以分解为各个m的贡献:每个m贡献m * 该区间中的x的数目,也就是m *m= m²。所以,整个总和s等于所有满足条件的m的平方之和,其中m对应的区间中的奇数是输入k中的一部分。
这样,问题转化为找出所有m,使得对应的区间中的奇数的x在1到k之间,并计算这些m的平方的总和,加上部分区间中的奇数的数目乘以对应的m。
例如,假设k=29,那么所有奇数到29对应的m是:
m=1:x=1 →贡献1²=1.
m=2:x=3,5 →贡献2²=4.
m=3:x=7,9,11 →贡献3²=9.
m=4:x=13,15,17,19 →贡献4²=16.
m=5:x=21,23,25,27,29 →贡献5²=25.
总和是1+4+9+16+25=55.
但根据题目中的例子,比如当k=21时,对应的总和是s=1+2+3+4+5=15吗?或者等一下,原题的示例中给出的例子:
例如,当k=5时,是求f(1)+f(3)+f(5)=1+2+2=5?或者假设题目中的示例:
比如,题目中的例子中:
f(1)=1 →因为1属于m=1的区间。
f(3)=2 →属于m=2的区间。
f(5)=2 →属于m=2的区间.
f(7)=3 →属于m=3的区间.
等等。
所以,当k=5时,奇数x有1,3,5。对应的f(x)总和是1+2+2=5。所以总和是5。但按照之前的思路,每个m对应的贡献是m²。例如,m=1贡献1²=1,m=2贡献2²=4,总和是1+4=5,与结果一致。这样,这说明当k刚好是某个区间的最后一个奇数时,总和为各m的平方之和。
当k位于某个区间的中间时,例如k=23,那么该区间对应的m=5的x包括21,23,25,27,29。此时,k=23,属于该区间的前两个数。所以,这部分的贡献是25。而前面的m=1,2,3,4贡献各自的平方。总和为1+4+9+16+25=1+4+9+16+10=40.
所以,现在问题转化为:
找到最大的m_max,使得其对应的区间的最后一个奇数 <=k。然后,计算所有m=1到m_max-1的平方之和,加上m_max对应的区间中奇数的数目(即有多少个奇数 <=k)乘以m_max.
例如,对于k=23:
m_max=5,因为该区间的最后一个奇数是29,而23 <29。所以,需要确定m_max的区间是否包含k。这里可能需要重新分析。
正确的做法应该是:
找到最大的m,使得该区间的起始奇数start <=k。然后,根据k的位置,确定该区间中有多少个奇数小于等于k,并计算贡献。
具体来说:
- 找出所有满足m的区间start <=k的m的最大值,即m_max。这里,start是m(m-1)+1。如果start <=k,则m是一个候选。
或者,更准确地说,找到最大的m,使得 start_m <=k,其中 start_m = m(m-1)+1.
然后,对于每个m,对应的奇数的范围是start_m到end_m =m(m+1)-1。每个区间内的奇数数目是m个。如果end_m <=k,那么该m的贡献是m²。否则,该m的贡献是 (k - start_m)/2 +1) *m.
所以,整个总和s可以分解为:
s = sum_{m=1}^{m_max-1} m² + (count) *m_max,
其中count是m_max区间中 <=k的奇数数目。
例如,当k=23时:
m_max=5,因为start_5=21 <=23,而start_6=6*5+1=31>23.
此时,end_5=29>23.所以,该区间的奇数是21,23,25,27,29. 其中,<=23的有21,23。数目是2。所以,贡献是2*5=10.
之前的m=1到4的贡献是1+4+9+16=30.总和30+10=40,即正确。
所以,现在的问题转化为如何快速找到m_max,并计算前m_max-1个平方和,以及当前m_max的贡献。
现在,如何找到m_max?
m_max是最大的m,使得 m(m-1)+1 ≤k.
这个不等式可以重写为:
m² -m +1 ≤k → m² -m ≤k-1 → m² -m - (k-1) ≤0.
解这个二次方程:
m = [1 ±√(1+4(k-1))]/2.
取正根:m = [1+√(4k-3)]/2.
所以,m_max= floor( [1+sqrt(4k-3)]/2 ).
例如,当k=23:
4k-3=89 → sqrt(89)=9.433 → [1+9.433]/2=5.216 → floor为5 → m_max=5.
当k=21时,4k-3=81 → sqrt=9 → [1+9]/2=5 → m_max=5.
当k=29时,4k-3=113 → sqrt≈10.63 → [1+10.63]/2≈5.815 → floor5 → m_max=5?
但k=29是m=5的区间的最后一个数,所以start=21 <=29,而且end=29=29 →属于该区间。所以m_max=5,正确。
那这个时候,m_max的计算可以简化为:
m_max = floor( (1+sqrt(4k-3))/2 )
这类似于之前f(x)的表达式,但现在是对k的处理。
现在,如何计算m_max?
同样,对于非常大的k(比如接近2^64-1),计算sqrt(4k-3)需要处理大数问题。但同样,可以使用二分法或者使用__uint128_t类型来处理。
所以,这里可能需要两个步骤:
步骤一:计算m_max,即最大的m满足m(m-1)+1 ≤k.
步骤二:计算该m_max对应的区间中有多少奇数<=k,即count= (k - start +2)/2. 因为区间内的奇数是连续的,步长为2,所以数目是 (k - start)/2 +1.
其中start= m_max*(m_max-1)+1.
如果 start +2*(m_max-1) <=k → count= m_max.
否则,count= (k - start)/2 +1.
例如,当k=23,start=21,k- start=2 → 2/2=1 → 1+1=2.
当k=29,count=5.
所以,count= min(m_max, (k - start)/2 +1).
所以,总和s= sum_{m=1}^{m_max-1} m² + count * m_max.
现在,问题分解为如何快速计算sum_{m=1}^n m²,其中n=m_max-1.
已知sum_{m=1}^n m² = n(n+1)(2n+1)/6.
所以,这部分可以用这个公式快速计算。
所以,现在,整个计算过程可以分为:
对于给定的k:
- 计算m_max = floor( (1+sqrt(4k-3))/2 ).
这可以通过二分法计算得到,其中,需要找到最大的m使得m(m-1)+1 ≤k.
-
计算sum_{m=1}^{m_max-1} m² = (m_max-1)m_max(2m_max-1)/6.
-
计算start =m_max*(m_max-1)+1.
-
计算count= min(m_max, (k - start)/2 +1).
这里,因为start和k的差必须>=0,所以如果k <start → count=0,但m_max的确定已经确保了start<=k,所以count≥0.
例如,当k=start时,count=1.
- 总和s= sum + count*m_max.
最后,将s mod 1e9+7.
现在,如何高效地计算m_max?
同样,可以使用二分法来找到最大的m满足m(m-1)+1 <=k.
例如,初始化low=1,high=一个足够大的数(比如sqrt(k)+2).
在每次循环中,计算mid,并判断mid*(mid-1)+1 <=k.
因为mid可能很大,所以mid*(mid-1)的计算可能溢出64位无符号整数。例如,当mid=232时,mid*(mid-1)=264 -232,这超过了64位无符号整数的最大值(264-1)。因此,在C++中,这样的计算会溢出,导致错误的结果。
因此,必须避免直接计算mid*(mid-1)+1,而使用某种方法来判断条件。
例如,可以将条件改写为:
mid*(mid-1) <=k-1.
这可能使用__uint128_t来进行比较。例如,在C++中:
__uint128_t product = (__uint128_t) mid * (mid-1);
if (product <=k-1) → 条件成立。
这样,可以安全地比较,即使mid很大。
所以,在实现中,可以将mid转换为__uint128_t类型进行计算。
因此,二分法的步骤为:
函数:计算m_max.
输入:k.
输出:最大的m,使得m(m-1)+1 <=k.
初始化low=1, high=2e32(足够大).
while low <=high:
mid= (low+high)/2.
__uint128_t product = (__uint128_t)mid * (mid-1);
if (product <=k-1 -1? → 原式是mid(mid-1)+1 <=k →等价于 mid(mid-1) <=k-1.
→判断product <=k-1.
→ 如果成立,则low=mid+1.
→否则,high=mid-1.
循环结束后,high就是最大的m满足条件。
例如,当k=23:
初始low=1,high=假设是23.
mid=12 → product=12*11=132 <=22? 132>22 →不成立。high=11.
mid=6 →6*5=30 <=22 →不成立。high=5.
mid=3 →3*2=6 <=22 →成立。low=4.
mid=4+5=4.5→4.5取整为4+5=9 →mid=4+5=9?或者,C++中整数除法,mid=(low+high)/2= (4+5)/2=4.5 →4.
mid=4 →4*3=12 <=22 →成立。low=5.
low=5, high=5.
mid=5 →5*4=20 <=22 →成立。low=6.
此时low>high,循环结束。high=5 → m_max=5.
正确。
所以,这样的二分法可以正确得到m_max的值。
现在,将所有步骤总结:
对于每个测试用例中的k:
-
使用二分法找到m_max,即最大的m满足m(m-1)+1 <=k.
-
计算sum_squares = m_max(m_max-1)(2*(m_max-1)+1)/6 →即sum_{m=1}^{m_max-1} m².
-
计算start =m_max*(m_max-1)+1.
-
计算count= (k - start)/2 +1 → 因为start和k都是奇数,所以k-start是偶数,所以count是整数。例如,start=21,k=23 →23-21=2 →2/2=1 →1+1=2.
-
如果count>m_max,说明k>= end,其中end=start+2(m_max-1) →这时count应该等于m_max.
否则,取count.
例如,当k >= end时,count= m_max.
end= start +2*(m_max-1) = m_max(m_max-1)+1 + 2m_max-2= m_max² -m_max +1 +2m_max-2= m_max² +m_max-1.
所以,当k >= m_max² +m_max-1 → count= m_max.
否则,count= (k - start)/2 +1.
所以,在代码中可以这样处理:
if (k >= start + 2*(m_max-1)) {
count = m_max;
} else {
count = (k - start)/2 +1;
}
或者,在计算count时,取 min(m_max, (k - start)/2 +1).
因为 start +2*(m_max-1) =m_max² +m_max -1.
所以,当k >=m_max² +m_max -1 →count=m_max.
否则,count= (k - start)/2 +1.
这样,就可以正确计算count的值。
然后,总和s= sum_squares + count *m_max.
最后,将s mod 1e9+7.
现在,剩下的问题是如何高效处理这些计算,特别是针对非常大的k值。
接下来,考虑取模的问题。由于最终结果需要mod 1e9+7,而中间的计算可能非常大,所以需要在每一步都进行mod,避免溢出。
例如,sum_squares的计算公式为 (m_max-1)m_max(2*(m_max-1)+1)/6 →但必须注意模运算的除法问题。因为6和模数1e9+7可能不是互质的,所以需要使用模的逆元。
但1e9+7是质数,6和它互质。所以,6的逆元可以预先计算。
例如,mod=1e9+7.
inv6=pow(6, mod-2, mod).
然后,sum_squares= (( (m_max-1) % mod ) * (m_max % mod) % mod ) * ( (2*(m_max-1)+1) % mod ) % mod ) * inv6 % mod.
这样,可以避免大数的计算。
同样,在处理count*m_max时,需要取模。
所以,整个计算流程必须在模意义下进行,以避免数值溢出。
现在,将所有步骤转换为代码:
预处理:
-
计算mod=1e9+7.
-
计算inv6=pow(6, mod-2, mod).
对于每个测试用例中的k:
-
计算m_max via二分法.
-
计算sum_squares = (m_max-1)m_max(2m_max-1)/6 mod mod.
注意,这里的分子可能非常大,所以需要使用公式转化为模运算,并利用逆元。
例如,sum_squares = ((m_max-1) * m_max % mod) * ( (2*(m_max-1) +1) % mod ) % mod ) * inv6 % mod.
-
计算start= m_max*(m_max-1)+1.
-
计算end=start +2*(m_max-1).
如果k >= end → count= m_max.
否则 → count= (k - start)/2 +1.
但需要注意,start和k都是奇数,所以k-start是偶数,所以除法是整数。
-
计算count_value= count % mod.
-
计算current_contribution= (count_value * (m_max % mod)) % mod.
-
总和s= (sum_squares + current_contribution) % mod.
输出s.
这样,代码就可以在O(1)的时间复杂度内处理每个测试用例,前提是m_max的计算是高效的。
现在,如何将m_max的二分法实现为高效的计算?
对于每个k,二分法的次数大约是log2(m_max)次。由于k可以达到2^64-1,m_max可能达到约 2^32。因此,二分法的次数大约是32次,这对于每个测试用例来说是可接受的,因为总共有3e6次测试用例,总次数是3e6 *32=9.6e7次操作,这在C++中是可以处理的。
现在,如何实现二分法?
实现步骤:
函数:计算最大的m,使得 m(m-1)+1 <=k.
输入:k的类型是unsigned long long.
输出:m_max的类型是unsigned long long.
实现:
使用二分法,其中low=1, high=初始值设置为足够大的数。例如,可以初始设置high=2e18,这足以覆盖k=264-1的情况。因为当m=232时,m(m-1)+1=2^64 -2^32 +1, which is larger than 264-1.所以,对于k=264-1来说,m_max=2^32-1.
或者,更精确地,可以通过数学推导得到high的上限。
例如,对于m(m-1)+1 <=k → m² -m <=k-1 → m² <=k-1 +m → m² <=k +m →忽略m,大致m^2 <=k +m → m<= sqrt(k)+1.
所以,high可以初始化为 sqrt(k)+2.
但计算sqrt(k)对于大的k可能需要使用浮点数,这可能不精确。所以,更安全的方式是将high初始化为 2e18,这足够大。
在C++中,可以将high初始化为 min( (unsigned long long)2e18, k ), 因为当k<1e18时,high取k更高效。但可能不需要,因为二分法的步骤会自动调整high。
所以,代码的大致结构:
unsigned long long compute_m_max(unsigned long long k) {
if (k ==0) return 0; // 但k>=1.
unsigned long long low=1, high= 2e18;
unsigned long long ans=0;
while (low <=high) {
unsigned long long mid= low + (high-low)/2;
__uint128_t product= (__uint128_t) mid * (mid-1);
if (product <= (__uint128_t)(k-1)) {
ans=mid;
low=mid+1;
} else {
high=mid-1;
}
}
return ans;
}
这样,就能得到正确的m_max.
例如,当k=23 →mid=5,product=5*4=20 <=22 →ans=5.
当k=1 →mid=1, product=0 <=0 →ans=1.
这样,函数是正确的。
现在,编写完整的C++代码:
需要注意以下几点:
-
处理模运算的中间步骤。
-
预处理inv6=166666668,因为 6*166666668 mod 1e9+7=1.
-
对于每个测试用例,读取k的值,调用compute_m_max(k)得到m_max.
-
计算sum_squares的部分,使用模运算。
-
计算start和count.
-
将结果取模。
代码示例:
#include <iostream>
using namespace std;
const int mod = 1e9 +7;
const int inv6= 166666668; // 6的逆元模1e9+7
unsigned long long compute_m_max(unsigned long long k) {
if (k ==0) return 0;
unsigned long long low=1, high= 2e18;
unsigned long long ans=0;
while (low <=high) {
unsigned long long mid= low + (high-low)/2;
__uint128_t product= (__uint128_t) mid * (mid-1);
if (product <= (__uint128_t)(k-1)) {
ans=mid;
low=mid+1;
} else {
high=mid-1;
}
}
return ans;
}
int main() {
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
int T;
cin >> T;
while (T--) {
unsigned long long k;
cin >> k;
unsigned long long m_max= compute_m_max(k);
if (m_max==0) { // 不可能,因为k>=1.
cout <<0 <<'\n';
continue;
}
// 计算sum_squares = sum_{m=1}^{m_max-1} m² → (m_max-1)*m_max*(2m_max-1)/6
unsigned long long n= m_max-1;
unsigned long long part1= n % mod;
unsigned long long part2= (n+1) % mod;
unsigned long long part3= (2*n +1) % mod;
unsigned long long sum_squares= (part1 * part2) % mod;
sum_squares= (sum_squares * part3) % mod;
sum_squares= (sum_squares * inv6) % mod;
// 计算start= m_max*(m_max-1)+1
__uint128_t start= (__uint128_t) m_max * (m_max-1) +1;
unsigned long long count;
if (k >= start + 2*(m_max-1)) {
count= m_max;
} else {
count= (k - start)/2 +1;
}
unsigned long long current= (count % mod) * (m_max % mod) % mod;
unsigned long long s= (sum_squares + current) % mod;
cout << s << '\n';
}
return 0;
}
这个代码的关键点在于:
-
使用__uint128_t来处理大数的乘法,避免溢出。
-
快速计算m_max,使用二分法。
-
使用模运算公式处理sum_squares.
测试样例:
例如,当k=1时,m_max=1.
sum_squares =0(因为 m_max-1=0).
start=1*0+1=1.
k >=1+0 →1 >=1 → count=1.
current=1*1=1.
总和s=0+1=1 mod 1e9+7 →1.
正确。
当k=3:
m_max的计算:
product=2*1=2 <=3-1=2 →成立。ans=2.
sum_squares是 sum for m=1到1 →1²=1.
sum_squares=1(1+1)(21+1)/6 → 12*3/6=1.
count: start=21+1=3. 3的区间的end=3+2(2-1)=5. k=3 >=3且<=5.
count= (3-3)/2 +1=0+1=1.
current=1*2=2.
总和=1+2=3.
但根据之前的例子,当k=3时,x=1和3。f(1)=1,f(3)=2 →总和是1+2=3。正确。
当k=5时,m_max=2的start=3 ≤5,product=2*1=2 <=5-1=4 →成立。ans=2.
sum_squares=1²=1.
start=2*1+1=3.
k=5 >=3+2*(2-1)=5 →count=2.
current=2*2=4.
总和1+4=5 →正确。
当k=23:
m_max=5.
sum_squares= (5-1)5(2(5-1)+1)/6 →459/6= 45*9=180 →180/6=30.
start=5*4+1=21.
end=21+2*4=29.
k=23 < 29 →count= (23-21)/2 +1=1+1=2.
current=2*5=10.
总和=30+10=40.
正确。
综上,代码是正确的。
输出结果
为了解决这个问题,我们需要快速计算给定正奇数 ( k ) 的 ( s = \sum_{i=1}^{(k+1)/2} f(2i-1) \mod 10^9 + 7 ),其中 ( f(x) ) 的定义是将 ( x ) 表示为某个连续奇数序列中的一个数时的序列长度。
方法思路
- 数学推导:通过观察发现,每个 ( m^3 ) 可以表示为 ( m ) 个连续奇数的和。我们需要找到每个奇数 ( x ) 所属的连续奇数序列的长度 ( m )。
- 二分法求最大 m:对于给定的 ( k ),使用二分法找到最大的 ( m ) 使得 ( m(m-1) + 1 \leq k )。
- 求和公式:利用数学公式计算前 ( m-1 ) 项的平方和,并结合当前 ( m ) 的贡献值来计算总和。
解决代码
#include <iostream>
using namespace std;
const int mod = 1e9 + 7;
const int inv6 = 166666668; // 6的模逆元,因为 6*166666668 ≡ 1 (mod 1e9+7)
unsigned long long compute_m_max(unsigned long long k) {
if (k == 0) return 0;
unsigned long long low = 1, high = 2e18;
unsigned long long ans = 0;
while (low <= high) {
unsigned long long mid = low + (high - low) / 2;
__uint128_t product = (__uint128_t)mid * (mid - 1);
if (product <= (__uint128_t)(k - 1)) {
ans = mid;
low = mid + 1;
} else {
high = mid - 1;
}
}
return ans;
}
int main() {
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
int T;
cin >> T;
while (T--) {
unsigned long long k;
cin >> k;
unsigned long long m_max = compute_m_max(k);
unsigned long long n = m_max - 1;
// 计算sum_squares = (n)(n+1)(2n+1)/6 mod mod
unsigned long long part1 = n % mod;
unsigned long long part2 = (n + 1) % mod;
unsigned long long part3 = (2 * n + 1) % mod;
unsigned long long sum_squares = part1 * part2 % mod;
sum_squares = sum_squares * part3 % mod;
sum_squares = sum_squares * inv6 % mod;
// 计算当前m_max的贡献
__uint128_t start = (__uint128_t)m_max * (m_max - 1) + 1;
unsigned long long end = start + 2 * (m_max - 1);
unsigned long long count;
if (k >= end) {
count = m_max;
} else {
count = (k - start) / 2 + 1;
}
unsigned long long current = (count % mod) * (m_max % mod) % mod;
unsigned long long s = (sum_squares + current) % mod;
cout << s << '\n';
}
return 0;
}
代码解释
- compute_m_max 函数:使用二分法找到最大的 ( m ) 使得 ( m(m-1) + 1 \leq k )。
- 主函数:
- 读取输入的 ( T ) 和每个测试用例的 ( k )。
- 计算 ( m_{\text{max}} ) 和前 ( m_{\text{max}} - 1 ) 项的平方和。
- 计算当前 ( m_{\text{max}} ) 的贡献值,并最终求和取模。
该方法通过数学推导和二分法高效地解决了大数问题,确保在合理时间内处理大量测试用例。

浙公网安备 33010602011771号