Find Integer(费马大定理)

博客围绕求解方程a^n+b^n=c^n中b和c的值展开。根据费马大定理排除n>2的情况,n=0无结果,n=1可随意取值,重点针对n=2,利用勾股数性质求解。

题目:

题意:

a^n+b^n=c^n,给出a,n,求出一组b,c;

分析:

根据费马大定理,n>2的所有情况可以排除,n=0无结果,n=1随便写,剩下的就是n=2,利用勾股数的性质就可以写出;

代码:

#include<stdio.h>
using namespace std;
int main()
{
    int t,a,n,b,c;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d %d",&n,&a);
        if(n>2||n==0)
        {
            printf("-1 -1\n");
            continue;
        }
        else if(n==1)
            printf("1 %d\n",1+a);
        else
        {
            if(a%2==1)
            {
                a=(a-1)/2;
                b=2*a*a+2*a;
                c=b+1;
            }
            else
            {
                if(a==4)
                {
                    b=3;
                    c=5;
                }
                else
                {
                    a=a/2;
                    b=a*a-1;
                    c=b+2;
                }
            }
            printf("%d %d\n",b,c);
        }
    }
    return 0;
}
<think>我们需要计算满足以下条件的整数三元组 (a, b, c) 的数量: 1. 1 ≤ a, b, c ≤ N 2. a, b, c 互不相同 3. a mod b = c 由于 N 最大可以达到 10^12,我们需要一个高效的算法(O(N) 或更优)。 注意条件:a mod b = c 意味着 a = b * k + c,其中 k 是整数(商),且满足 0 ≤ c < b。但题目中 c 的范围是 1 到 N,而根据模运算的定义,c 必须在 [0, b-1] 范围内。因此,条件 c ≥ 1 意味着 c 至少为 1,所以 b 至少为 2(因为 c < b 且 c≥1,所以 b>1)。 另外,三个数互不相同。 然而,题目要求 a, b, c 互不相同,所以 a, b, c 两两不同。 我们重新审视条件:a = b * k + c,其中 k 是整数(k≥0),且 1≤c≤b-1(因为c≥1且c<b)。 但是,题目要求 c 的范围是 1 到 N,而这里 c 在 [1, b-1] 内,所以自然满足 c≤N(因为b≤N,所以c≤b-1≤N-1)。 因此,我们可以将问题转化为:对于每个 b(从 2 到 N,因为b至少为2),然后对于每个可能的 c(1 到 b-1),然后 a = b * k + c 必须在 [1, N] 范围内,且 a 必须与 b 和 c 不同。 注意:a 已经与 b 不同(因为 a = b*k + c,而 c 在 [1,b-1] 内,所以 a 不可能等于 b,除非 k=0 且 c=b,但c<b,所以不可能)。同样,a 与 c 的关系:可能相等吗?我们需要检查。 另外,题目要求三个数互不相同,所以 a, b, c 两两不同。 因此,我们需要满足: a ≠ b, a ≠ c, b ≠ c. 由条件,b≠c 是显然的(因为c在[1,b-1]内,所以c≠b)。而 a 与 b:由于 a = b*k + c,如果 k≥1,则 a≥b+c≥b+1>b,所以 a≠b;如果 k=0,则 a=c,此时 a=c,但题目要求三个数互不相同,所以这种情况不允许。因此,我们要求 k≥1,这样 a=c 的情况就不会发生(因为k≥1时,a = b*k + c ≥ b + c ≥ b+1 > c,所以 a>c,因此a≠c)?但是,当k=0时,a=c,这是不允许的。所以我们必须排除k=0的情况。 因此,我们要求 k≥1。 但是,我们还需要注意 a 是否等于 c?当 k≥1 时,a = b*k + c ≥ b + c ≥ c+1 (因为b≥2,所以b≥c+1?不一定,因为c最大为b-1,所以b≥c+1成立)。因此,a ≥ b + c ≥ (c+1) + c = 2c+1 > c,所以 a ≠ c。同样,a 也不会等于 b(因为 a≥b+1)。 所以,我们只需要考虑 k≥1 的情况。 因此,三元组 (a,b,c) 的个数可以表示为: ∑_{b=2}^{N} ∑_{c=1}^{b-1} [满足 a = b*k + c ≤ N 且 k≥1 的整数 k 的个数] 对于固定的 b 和 c,k 的取值范围是:k≥1 且 a = b*k + c ≤ N,即 k ≤ (N - c) / b。 所以 k 的取值范围为:1 ≤ k ≤ floor((N - c) / b) 因此,对于固定的 b 和 c,满足条件的 k 的个数是:max(0, floor((N - c) / b)) 所以,总数为: ans = ∑_{b=2}^{N} ∑_{c=1}^{b-1} floor((N - c) / b) 但是,这个式子没有考虑 a, b, c 互不相同?我们之前已经通过要求k≥1保证了a≠b且a≠c,并且b≠c(因为c在[1,b-1]内)。所以这个式子已经满足互不相同的条件。 然而,我们还需要注意:在同一个三元组中,a, b, c 两两不同,但这里我们通过构造(c在[1,b-1]内,k≥1)保证了。但是,有没有可能a等于另一个数?比如,a等于另一个b?不会,因为我们的求和是分别对b和c进行的,a由b和c以及k确定。而且,同一个a可能出现在不同的b和c的组合中?但是,题目要求的三元组是(a,b,c),所以只要三元组不同,即使a相同,但b和c不同,也算不同的三元组。所以我们的计数是按三元组计数。 但是,我们还需要注意:同一个三元组(a,b,c)是否会被重复计数?不会,因为每个三元组由(a,b,c)唯一确定,而我们的求和中,b和c固定,然后k由a确定,所以每个三元组只被计数一次。 因此,问题转化为计算: ans = ∑_{b=2}^{N} ∑_{c=1}^{b-1} floor((N - c) / b) 但直接计算这个二重循环,b从2到N,c从1到b-1,时间复杂度是O(N^2),而N=1e12,显然不行。 我们需要优化这个求和。 首先,交换求和次序?注意内层求和是c从1到b-1,所以我们可以写成: ans = ∑_{b=2}^{N} S(b) 其中 S(b) = ∑_{c=1}^{b-1} floor((N - c) / b) 然后,我们考虑将求和中的c变量替换为t = c,那么: S(b) = ∑_{c=1}^{b-1} floor((N - c) / b) 令 T = floor((N-1)/b) (因为c=1时,分子为N-1) 但是,我们发现随着c的增加,分子N-c在减少,所以我们需要分析这个求和。 实际上,我们可以将求和拆分为: S(b) = ∑_{c=1}^{b-1} floor((N - c) / b) = [ ∑_{c=1}^{b-1} floor(N/b - c/b) ] 但这不是整数除法,所以不能这样拆分。我们需要利用整数除法的性质。 注意:对于固定的b,我们可以将求和写成: S(b) = ∑_{c=1}^{b-1} floor((N - c) / b) = ( ∑_{c=1}^{b} floor((N - c) / b) ) - floor((N-b)/b) [因为c=b时,项为floor((N-b)/b),但c的范围是1到b-1,所以也可以考虑c从0到b-1?] 但是,如果我们令 c' = c,那么当c从1到b-1时,c'的取值使得N-c'从N-1到N-b+1。 我们可以考虑变换:令 j = N - c,则当c=1时,j=N-1;c=b-1时,j=N-b+1。所以: S(b) = ∑_{j=N-b+1}^{N-1} floor(j/b) [注意:j从N-b+1到N-1] 但是,这个区间长度是b-1,且是连续的一段。然而,我们更希望得到的是从某个整数到某个整数关于b的整数除法的求和。 另一种思路:利用整数除法的分段常数性质。 我们考虑将求和式改写: S(b) = ∑_{c=1}^{b-1} \left( \left\lfloor \frac{N}{b} \right\rfloor - \left\lceil \frac{c}{b} \right\rceil \right) ??? 不对。 实际上,我们有: (N - c) = b * q + r, 0≤r<b,所以 floor((N-c)/b) = q。 但是,我们可以将求和拆成: S(b) = ∑_{c=1}^{b-1} \left( \left\lfloor \frac{N}{b} \right\rfloor - \left\lfloor \frac{N \mod b + (b - c)}{b} \right\rfloor \right) 这个变换并不明显。 一个常见的技巧是:注意到 ∑_{c=0}^{b-1} floor((N - c) / b) = floor(N/b) + floor((N-1)/b) + ... + floor((N-b+1)/b) 而我们知道,对于任意整数N和b,有: ∑_{c=0}^{b-1} floor((N - c) / b) = floor(N/b) * b + ... 并不简单。 实际上,我们有恒等式: ∑_{c=0}^{b-1} floor((N - c) / b) = floor(N/b) * b + \sum_{c=0}^{b-1} [ (N mod b) >= c ? 1 : 0 ] ??? 不,这个不对。 更常见的恒等式是: ∑_{c=0}^{b-1} floor((N + c) / b) = N + b - gcd(N,b) [这个恒等式存在吗?] 实际上,有一个著名的恒等式(除法求和): ∑_{c=0}^{b-1} floor((N + c) / b) = N + b - (N \mod b) [不对] 事实上,我们考虑: ∑_{c=0}^{b-1} floor((N + c) / b) = floor(N/b) + floor((N+1)/b) + ... + floor((N+b-1)/b) 而我们知道,这个和等于 N + (b-1) ??? 不一定。 实际上,我们有: ∑_{c=0}^{b-1} floor((N + c) / b) = floor(N/b) * b + (b-1) [这个也不对] 另一个思路:利用 ∑_{c=1}^{b-1} floor((N - c) / b) = ∑_{k=0}^{b-1} k * (满足 floor((N-c)/b)=k 的c的个数) 但这样还是需要分段。 我们换一个角度:考虑整个求和 ans = ∑_{b=2}^{N} ∑_{c=1}^{b-1} floor((N-c)/b) 改变求和次序,先枚举b,再枚举c。但是,我们可以尝试枚举b,然后利用数论分块加速内层求和?或者对b进行分块? 注意到:对于固定的b,内层求和是c从1到b-1,而函数 floor((N-c)/b) 关于c是线性递减的,但递减速度是常数(每次减1/b),所以很难直接分段。 但是,我们可以将内层求和写成: S(b) = ∑_{c=1}^{b-1} floor((N - c) / b) = ∑_{c=1}^{b-1} \left( \left\lfloor \frac{N}{b} \right\rfloor - 1 + \left[ (N \mod b) \geq c \right] \right) ??? 不对。 实际上,令 r = N mod b,即 N = b * q + r, 0≤r<b。 则 N - c = b*q + r - c,所以: floor((N-c)/b) = q + floor((r - c) / b) 当 c ≤ r 时,r-c ≥0,但 r-c < b(因为r<b,c≥1,所以r-c≤r-1≤b-1),所以 floor((r-c)/b)=0?不对,因为r-c可能是负数(当c>r时)?注意c从1到b-1,r在[0,b-1]内。 所以: 当 c ≤ r 时:r-c ≥0,且r-c < b,所以 floor((r-c)/b)=0,因此 floor((N-c)/b) = q。 当 c > r 时:r-c <0,且|r-c| < b(因为c≤b-1,所以r-c≥r-(b-1)≥-(b-1)),所以 r-c 在 [-b+1, -1] 内,因此 floor((r-c)/b) = -1,所以 floor((N-c)/b)=q-1。 因此: S(b) = ∑_{c=1}^{b-1} floor((N-c)/b) = ∑_{c=1}^{r} q + ∑_{c=r+1}^{b-1} (q-1) = r * q + (b-1 - r) * (q-1) 而 q = floor(N/b),r = N mod b = N - b*q。 所以: S(b) = r * q + (b-1 - r) * (q-1) = r*q + (b-1)(q-1) - r*(q-1) = r*q + (b-1)(q-1) - r*q + r = (b-1)(q-1) + r 将 r = N - b*q 代入: S(b) = (b-1)(q-1) + (N - b*q) = (b-1)(q-1) + N - b*q = (b-1)*q - (b-1) + N - b*q = N - (b-1) - q = N - b + 1 - q 所以,我们得到: S(b) = N - b + 1 - floor(N/b) 验证:例如N=5, b=2: 则c=1: floor((5-1)/2)=2,所以S(2)=2。 公式:N-b+1 - floor(N/b) = 5-2+1 - 2 = 2,正确。 再验证:b=3,N=5: c=1: floor((5-1)/3)=1; c=2: floor((5-2)/3)=1; 所以S(3)=1+1=2。 公式:5-3+1 - floor(5/3)=3-1=2,正确。 因此,我们有: ans = ∑_{b=2}^{N} [N - b + 1 - floor(N/b)] 所以: ans = ∑_{b=2}^{N} (N - b + 1) - ∑_{b=2}^{N} floor(N/b) 第一部分:∑_{b=2}^{N} (N - b + 1) = ∑_{k=2}^{N} (N - k + 1) [令k=b] = ∑_{j=1}^{N-1} j [令 j = N - k + 1, 当k=2时,j=N-1;k=N时,j=1。所以j从1到N-1] = (N-1)*N/2 第二部分:∑_{b=2}^{N} floor(N/b) 因此: ans = (N-1)*N/2 - ∑_{b=2}^{N} floor(N/b) 但是,注意:我们原来的求和是从b=2到N,而第一部分是等差数列求和。 然而,我们还需要考虑什么?在推导过程中,我们假设了b≥2,并且c在[1,b-1]内,并且我们推导出了S(b)的表达式,所以这个公式应该是正确的。 但是,我们还需要注意:在原始问题中,a,b,c必须两两不同,而我们通过要求k≥1(即a≥b+1)保证了a与b、a与c都不相同,并且b≠c(因为c在[1,b-1]内)。所以这个公式计算的是满足条件的三元组数。 但是,我们是否漏掉了什么?在条件中,a,b,c必须是三个不同的整数,但这里我们只保证了a≠b, a≠c, b≠c,所以没有遗漏。 因此,答案就是: ans = (N-1)*N/2 - ∑_{b=2}^{N} floor(N/b) 但是,我们还需要注意:当b>N时,floor(N/b)=0,所以求和可以写成从b=1到N,然后减去b=1的项(即floor(N/1)=N)。所以: ∑_{b=2}^{N} floor(N/b) = ∑_{b=1}^{N} floor(N/b) - N 因此: ans = (N-1)*N/2 - [ ∑_{b=1}^{N} floor(N/b) - N ] = (N-1)*N/2 - ∑_{b=1}^{N} floor(N/b) + N = [ (N^2 - N)/2 + N ] - ∑_{b=1}^{N} floor(N/b) = (N^2 - N + 2N)/2 - ∑_{b=1}^{N} floor(N/b) = (N^2 + N)/2 - ∑_{b=1}^{N} floor(N/b) 而 ∑_{b=1}^{N} floor(N/b) 是数论中常见的求和,它等于1到N中每个数的约数个数和(即D(N) = ∑_{k=1}^{N} d(k)),其中d(k)是k的约数个数?不对。 实际上,∑_{b=1}^{N} floor(N/b) 表示的是:对于每个b,b在1到N中,b作为约数出现的次数(即b整除1到N中的数的次数)?不对。 实际上,∑_{b=1}^{N} floor(N/b) 等于 ∑_{k=1}^{N} d(k) 吗?不是。 注意:∑_{b=1}^{N} floor(N/b) = ∑_{b=1}^{N} ∑_{a=1}^{N} [b|a] [因为b整除a的个数为floor(N/b)],所以这个和等于 ∑_{a=1}^{N} d(a),即1到N的约数个数和。 所以,我们有: ans = (N^2 + N)/2 - D(N) 其中 D(N) = ∑_{k=1}^{N} d(k) 是约数个数和函数。 但是,我们之前推导的公式是: ans = (N^2 + N)/2 - D(N) 然而,我们还需要验证一个小例子。 取N=2: 满足条件的三元组:不存在,因为要求三个不同的数,而范围只有1,2,所以无法选出三个不同的数。所以答案应该是0。 计算:D(2)=d(1)+d(2)=1+2=3, (2^2+2)/2=3,所以ans=3-3=0,正确。 取N=3: 可能的三元组:我们需要满足 a mod b = c,且a,b,c是1,2,3的排列,且互不相同。 尝试: a=3, b=2, c=1: 3 mod 2 = 1 -> 满足。 a=3, b=1, c=2: 3 mod 1 = 0,但要求c=2,0≠2 -> 不满足。 a=2, b=1, c=3: 2 mod 1=0≠3 -> 不满足。 a=2, b=3, c=1: 2 mod 3=2≠1 -> 不满足。 a=1, b=2, c=3: 1 mod 2=1≠3 -> 不满足。 a=1, b=3, c=2: 1 mod 3=1≠2 -> 不满足。 所以只有一组:(3,2,1) 计算:D(3)=d(1)+d(2)+d(3)=1+2+2=5, (3^2+3)/2=6,所以ans=6-5=1,正确。 取N=4: 我们计算:D(4)=d(1)+d(2)+d(3)+d(4)=1+2+2+3=8, (4^2+4)/2=10,所以ans=10-8=2。 我们枚举: (a,b,c) 满足:1≤a,b,c≤4,互不相同,且a mod b = c。 可能的: (3,2,1): 3 mod 2 = 1 -> 满足。 (4,2,0): 但c=0不允许(题目要求c≥1),所以不行。 (4,3,1): 4 mod 3 = 1 -> 满足。 (4,1,?):不行,因为模1等于0,而c要求≥1且与b=1不同(但c不能为0,所以不行)。 (2,3,?):2 mod 3=2,如果c=2,但要求三个数互不相同:这里b=3,c=2,那么a=2,但是a=2与c=2相同,所以不行(注意:我们的推导中,a=2, b=3, c=2是不允许的,因为a=c)。所以只有两组:(3,2,1)(4,3,1)。 所以答案是2,正确。 因此,公式为: ans = (N*(N+1))//2 - D(N) 其中 D(N) = ∑_{k=1}^{N} d(k) 是约数个数和。 现在问题转化为:如何高效计算 D(N) 当 N=1e12? 计算 D(N) 的标准方法是用数论分块(整除分块),时间复杂度 O(sqrt(N))。 具体方法: D(N) = ∑_{k=1}^{N} d(k) = ∑_{k=1}^{N} ∑_{d|k} 1 = ∑_{d=1}^{N} floor(N/d) 注意:这个等式我们已经知道,所以 D(N) = ∑_{d=1}^{N} floor(N/d) 但是,我们之前已经用这个表达式了。现在需要计算 ∑_{d=1}^{N} floor(N/d) 当 N=1e12。 我们可以通过枚举整除分块来求和。具体地,对于 i 从 1 到 sqrt(N),我们计算 floor(N/i) 的取值,并统计每个取值出现的次数。 算法: 令 s = 0 令 i = 1 while i <= N: j = N // (N // i) # 使得 floor(N/i) = floor(N/j) 的最大j s += (j - i + 1) * (N // i) i = j + 1 这样,我们可以在 O(sqrt(N)) 的时间内计算 D(N)。 因此,整个算法流程: 输入:N 如果 N < 3: 则直接输出0(因为不可能有互不相同的三元组) 计算 D(N) = ∑_{d=1}^{N} floor(N/d) [注意:这个D(N)就是约数个数和] 计算 ans = (N*(N+1))//2 - D(N) [注意:这里 (N*(N+1))//2 是整数,因为N或N+1是偶数] 输出 ans % 998244353 但是,注意:我们推导的公式中,求和b是从2到N,然后我们转换成了 ∑_{b=1}^{N} floor(N/b) 减去 b=1的项(即N),然后加上一个等差数列。最后得到 (N(N+1))/2 - D(N)。所以正确。 然而,我们还需要注意:在公式中,第一部分 (N(N+1))/2 表示的是所有满足1≤a,b,c≤N且互不相同的三元组的总数吗?不对,我们推导中第一部分是等差数列求和,对应的是 ∑_{b=2}^{N} (N-b+1) = (N-1)*N/2,然后加上一个N,变成了(N^2+N)/2,这个值实际上等于 C(N,2) + N ?不对,它等于 (N^2+N)/2,而总三元组数应该是 N^3 量级。但注意,我们这里并不是在枚举所有三元组,而是通过条件转换后的表达式。 所以,我们直接使用公式即可。 但是,注意:当N很大时,N*(N+1)/2 可能超过long long的范围(N=1e12,则N*(N+1)≈1e24,在C++中可以用unsigned long long或__int128,但题目要求模998244353,所以我们可以模运算下进行。 但是,注意:我们最后要取模,而D(N)也要取模。然而,D(N)可能非常大,我们需要在计算过程中取模吗? 注意:我们要求的是 ans = (N*(N+1)/2 - D(N)) mod 998244353 但是,N*(N+1)/2 是整数,而 D(N) 是整数,所以我们可以分别计算模998244353下的值,然后相减再取模。 但是,注意:N*(N+1)/2 可能不是整数?不,因为N或N+1是偶数,所以整除2是整数。 但是,在模运算下,除法2需要乘以逆元。 所以: part1 = (N % mod) * ((N+1) % mod) % mod * inv2 % mod [其中inv2是2在模998244353下的逆元] 而 D(N) 是一个很大的整数,但是我们可以一边求和一边取模吗?注意:我们要求的是 D(N) mod 998244353,但是 D(N) 可能非常大(大约1e12 * 1e12?不对,D(N) ≈ N log N,所以当N=1e12时,D(N)≈1e12*12*log10(1e12)≈1e12*12*12≈1.44e15,这个值小于2^60,所以可以用long long存储?但是取模后,我们只需要模998244353的值。 但是,注意:我们计算的D(N)是整数,而我们在计算 ∑_{d=1}^{N} floor(N/d) 时,可以用整除分块,每次累加一个区间内的和,然后在累加过程中取模。 但是,这里有一个问题:整除分块中,每个块的值是 (j-i+1) * (N//i),这个值可能很大(最大为 N * sqrt(N) ≈ 1e12 * 1e6 = 1e18,在C++中long long最大为9e18,所以1e18可以存储,但是取模后我们可以用long long存储中间结果,然后每一步取模。 因此,我们可以这样计算D(N) mod mod: D = 0 i = 1 while i <= N: j = N // (N // i) # 当前块的上界 # 当前块的值:t = (N // i) * (j - i + 1) t = (N // i) % mod; # 但是注意:N//i是一个很大的整数(最大1e12),而(j-i+1)也可能很大(最大N),所以直接乘会溢出? # 我们需要计算:t = (N // i) * (j - i + 1) % mod # 但是,j-i+1 可能很大,所以不能直接乘,需要先取模? 但是,注意:我们要求的是整个和模mod,所以: t = ( (N // i) % mod ) * ( (j - i + 1) % mod ) % mod 然后 D = (D + t) % mod 但是,这样不对,因为 (N//i)(j-i+1) 都是大数,它们的乘积可能超过long long的范围(最大1e12 * 1e12 = 1e24,而long long最大约9e18,所以会溢出)。 所以,我们需要避免溢出。我们可以使用模乘法的技巧,或者使用__int128(如果编译器支持)。或者,我们可以在乘法之前先取模,但是这样会丢失中间值?因为我们需要的是乘积模mod,所以我们可以用: t = ( (N // i) % mod ) * ( (j - i + 1) % mod ) % mod; 但是,这样计算的是 [ (N//i) mod mod ] * [ (j-i+1) mod mod ] mod mod,而实际上我们需要的是 ( (N//i) * (j-i+1) ) mod mod。 由于 (a mod mod) * (b mod mod) mod mod = (a*b) mod mod,所以这样计算是正确的。 但是,注意:N//i 和 (j-i+1) 都是整数,所以这样计算没问题。 但是,当 N//i 很大(比如1e12)时,取模后变成1e12%998244353(约1e12,而998244353约1e9,所以1e12%998244353=1e12-1000*998244353,计算量不大?)而(j-i+1)最大为N(1e12),所以 (j-i+1)%mod 也是类似。然后两个数相乘最大为 (1e9) * (1e9) = 1e18,在long long范围内(long long最大9e18),所以不会溢出。 但是,实际上:N//i 最大为N(当i=1时,N//i=N),而j-i+1最大为N(当i=1时,j=1,所以j-i+1=1;当i=2时,j可能很大,但不会超过N)。所以乘积最大为 N * N = 1e24,而1e24超过了long long的范围(long long最大约9e18),所以我们需要用__int128来存储中间乘积,然后再取模。 但是,题目要求N=1e12,所以 N//i 最大1e12,而j-i+1最大也是1e12,乘积1e24,而__int128可以存储(最大约3e38)。 所以,我们可以用__int128来存储中间乘积,然后取模。或者,我们使用模乘法的技巧,避免直接相乘。 另一种方法:使用函数计算模下的乘积: t = mul_mod( (N//i) % mod, (j-i+1) % mod, mod) 其中 mul_mod(x,y,mod) 计算 (x*y) % mod,且避免溢出。 但是,由于x和y都在0到mod-1之间?不对,我们取模后,x = (N//i) % mod,y = (j-i+1) % mod,所以x,y都在0到mod-1(998244353-1)之间。那么x*y最大为 (1e9)^2=1e18,在long long范围内(因为1e18<9e18),所以可以直接相乘再取模。 因此,我们可以这样: t1 = (N // i) % mod; t2 = (j - i + 1) % mod; t = (t1 * t2) % mod; 然后 D = (D + t) % mod; 但是,注意:这样计算的是 ( (N//i) * (j-i+1) ) mod mod,而实际上我们需要的是整个块的和模mod,所以这样计算正确。 但是,这样计算的是每个块对D的贡献模mod后的值,然后累加。所以最后D就是 ∑_{d=1}^{N} floor(N/d) mod mod。 然而,这里有一个问题:整除分块中,每个块的和是 (j-i+1)*(N//i),这个值可能很大,但我们只关心模mod的值。所以这样计算是可行的。 但是,注意:在整除分块中,i和j都是整数,且j是当前块的最后一个索引,由 j = N // (N // i) 得到。 因此,计算D(N)的代码: long long D = 0; long long mod = 998244353; long long inv2 = (mod+1)/2; // 2的逆元,这里用不到 for (long long i = 1, j; i <= N; i = j + 1) { long long q = N / i; if (q == 0) break; j = N / q; // j是使得 floor(N/t)=q 的最大t // 这个块中,i到j,每个d对应的值都是q long long t1 = q % mod; long long t2 = (j - i + 1) % mod; long long t = t1 * t2 % mod; D = (D + t) % mod; } 但是,这样计算的是 D(N) mod mod,而我们需要的是 D(N) 的精确值模mod?是的,因为最后答案要模mod。 但是,这样计算正确吗?注意:我们实际上要求的是 ∑_{d=1}^{N} floor(N/d) 模mod,而我们将每个块的和模mod后累加。所以正确。 但是,我们需要注意:在求和过程中,每个块的和是 (j-i+1)*q,我们计算的是这个乘积模mod,然后累加模mod。所以最后D就是整个和模mod。 但是,我们还需要注意:当N很大时,循环次数是O(sqrt(N)),所以可以接受。 然后,计算: part1 = (N % mod) * ((N+1) % mod) % mod * inv2 % mod; // 即 (N(N+1)/2) mod mod ans = (part1 - D + mod) % mod; // 注意减法取模 最后输出ans。 但是,这里有一个问题:在整除分块中,我们计算的是 D(N) = ∑_{d=1}^{N} floor(N/d) mod mod,而实际上,我们推导的公式是: ans = (N(N+1)/2) - D(N) 而这里,我们计算的是模意义下的减法。所以正确。 但是,我们还需要验证小例子:N=3 D(3) = floor(3/1)+floor(3/2)+floor(3/3)=3+1+1=5 (3*4/2)=6, 6-5=1. 模998244353:part1=6 mod mod=6, D=5 mod mod=5, ans=(6-5)=1. N=4: part1=10, D(4)=4+2+1+1=8, ans=2. 所以正确。 但是,注意:在整除分块中,我们计算的是 ∑_{d=1}^{N} floor(N/d),当d>N时,值为0,所以d从1到N。 因此,代码实现如下: 步骤: 1. 输入N(最大1e12) 2. 如果N<3,输出0。 3. 计算 part1 = (N*(N+1)/2) mod 998244353 (注意:先取模再乘逆元) 实际上:因为N*(N+1)是偶数,我们可以: part1 = ( (N % mod) * ((N+1) % mod) % mod ) * inv2 % mod; 其中 inv2 = 499122177 (因为2*499122177 mod 998244353=1) 4. 计算 D = ∑_{d=1}^{N} floor(N/d) mod mod (使用整除分块,注意中间用乘法取模) 5. ans = (part1 - D + mod) % mod; 6. 输出ans 但是,注意:整除分块中,j可能会超过N?不会,因为 j = N // q,而 q = N // i ≥ 1,所以 j = N // q ≤ N。 另外,当i>N时,循环结束。 因此,代码: #include <iostream> using namespace std; typedef long long ll; const ll mod = 998244353; const ll inv2 = (mod+1)/2; // 因为2*inv2=mod+1,所以模mod下等于1 int main() { ll N; cin >> N; if (N < 3) { cout << 0 << endl; return 0; } // 计算 part1 = N*(N+1)/2 mod mod ll part1 = (N % mod) * ((N+1) % mod) % mod; part1 = part1 * inv2 % mod; // 计算 D = sum_{d=1}^{N} floor(N/d) mod mod ll D = 0; for (ll i = 1, j; i <= N; i = j + 1) { ll q = N / i; if (q == 0) break; j = N / q; // j is the maximum index such that floor(N/i)=q // 这个块:i到j,每个d的值为q,共 (j-i+1) 个数 // 累加: (q % mod) * ((j-i+1) % mod) % mod ll t1 = q % mod; ll t2 = (j - i + 1) % mod; ll t = t1 * t2 % mod; D = (D + t) % mod; } ll ans = (part1 - D + mod) % mod; cout << ans << endl; return 0; } 但是,我们还需要注意:在整除分块中,当i>N时,循环结束,所以不会出现i<=N而q=0的情况?因为当i>N时,N/i=0,但循环条件i<=N,所以我们在循环内判断q==0时break。 另外,当N很大时,整除分块的循环次数是O(sqrt(N)),所以可以处理N=1e12。 但是,我们还需要测试一下N=1e12的运行时间。循环次数大约2*sqrt(N)=2e6,在C++中可接受。 因此,我们按照这个思路写代码即可。 但是,我们还需要注意:在计算(j-i+1)时,j和i都是long long,所以不会溢出。 最后,输出答案。 我们测试一下N=1,2,3,4,然后大数用公式验证。 例如,N=1e12,我们无法直接计算D(N),但我们可以用整除分块,所以代码可以运行。 注意:在计算过程中,取模操作不会影响最终结果(模意义下正确)。 因此,我们输出代码即可。 但是,题目要求的是三元组数量,我们已经推导出公式,所以代码如上。 总结:问题转化为计算 (N(N+1)/2 - D(N)) mod 998244353,其中 D(N)=∑_{k=1}^{N} floor(N/k) 注意:D(N) 就是约数个数和函数(即1到N的约数个数之和)。 所以,最终代码实现如上。 但是,我们之前推导的公式中,第一部分 (N(N+1))/2 是什么?它并不是所有三元组的个数,而是通过转换得到的表达式。所以直接使用公式即可。 因此,我们写代码并测试。 注意:在计算过程中,取模时,要保证非负:ans = (part1 - D + mod) % mod; 最后,生成相关问题: 1. 如何计算1到N的约数个数和? 2. 整除分块(数论分块)的原理是什么? 3. 模运算下除法(乘以逆元)如何实现? 4. 还有哪些类似问题可以用整除分块优化?</think>### 问题分析 需要计算满足以下条件的整数三元组 \((a, b, c)\) 的数量: 1. \(1 \leq a, b, c \leq N\) 2. \(a, b, c\) 互不相同 3. \(a \mod b = c\) 其中 \(N \leq 10^{12}\),结果需对 \(998244353\) 取模。直接枚举三元组的时间复杂度为 \(O(N^3)\),无法满足要求,需设计高效算法。 ### 关键推导 通过数学变换,问题可转化为: 1. 由条件 \(a \mod b = c\) 得 \(a = b \cdot k + c\),其中 \(k \geq 1\)(确保 \(a \neq c\)),且 \(1 \leq c \leq b-1\)(确保 \(b \neq c\))。 2. 三元组数量等价于: \[ \text{ans} = \sum_{b=2}^{N} \sum_{c=1}^{b-1} \left\lfloor \frac{N - c}{b} \right\rfloor \] 3. 进一步化简为: \[ \text{ans} = \frac{N(N+1)}{2} - \sum_{k=1}^{N} \left\lfloor \frac{N}{k} \right\rfloor \] 4. 其中 \(\sum_{k=1}^{N} \left\lfloor \frac{N}{k} \right\rfloor\) 是约数个数和函数 \(D(N)\),表示 \(1\) 到 \(N\) 所有整数的约数个数之和。 ### 高效计算 1. **计算 \(\frac{N(N+1)}{2} \mod 998244353\)**: - 利用公式直接计算,注意使用模乘和乘法逆元(2 的逆元为 \(499122177\))。 2. **计算 \(D(N) = \sum_{k=1}^{N} \left\lfloor \frac{N}{k} \right\rfloor \mod 998244353\)**: - 使用 **整除分块(数论分块)** 优化,时间复杂度 \(O(\sqrt{N})\)。 - 核心思想:将 \(\left\lfloor \frac{N}{k} \right\rfloor\) 相同的 \(k\) 分到同一块,每块求和。 ### 代码实现 ```cpp #include <iostream> using namespace std; typedef long long ll; const ll MOD = 998244353; const ll INV_2 = 499122177; // 2的逆元,因为2*499122177 ≡ 1 (mod 998244353) int main() { ll N; cin >> N; if (N < 3) { cout << 0 << endl; return 0; } // 计算 part1 = N(N+1)/2 mod MOD ll part1 = (N % MOD) * ((N + 1) % MOD) % MOD; part1 = part1 * INV_2 % MOD; // 计算 D(N) = ∑_{k=1}^{N} floor(N/k) mod MOD ll D = 0; for (ll i = 1, j; i <= N; i = j + 1) { ll q = N / i; // 当前块的商 if (q == 0) break; j = N / q; // 当前块的右边界 // 计算当前块贡献: [i, j] 区间内所有 k 的 floor(N/k) = q ll block_size = (j - i + 1) % MOD; ll block_value = q % MOD; D = (D + block_size * block_value) % MOD; } // 最终结果: ans = part1 - D ll ans = (part1 - D + MOD) % MOD; cout << ans << endl; return 0; } ``` ### 复杂度分析 - **时间复杂度**:\(O(\sqrt{N})\),整除分块最多循环 \(2\sqrt{N}\) 次。 - **空间复杂度**:\(O(1)\),仅用常数空间。 ### 示例验证 - **输入 \(N = 3\)**: - 满足条件的三元组:\((a,b,c) = (3,2,1)\)(因 \(3 \mod 2 = 1\))。 - 计算:\(\frac{3 \times 4}{2} = 6\),\(D(3) = \left\lfloor \frac{3}{1} \right\rfloor + \left\lfloor \frac{3}{2} \right\rfloor + \left\lfloor \frac{3}{3} \right\rfloor = 3 + 1 + 1 = 5\)。 - 结果:\(6 - 5 = 1\),输出 \(1\)。 - **输入 \(N = 4\)**: - 满足条件的三元组:\((3,2,1)\) 和 \((4,3,1)\)。 - 计算:\(\frac{4 \times 5}{2} = 10\),\(D(4) = 4 + 2 + 1 + 1 = 8\)。 - 结果:\(10 - 8 = 2\),输出 \(2\)。 ### 相关问题 1. **如何高效计算约数个数和函数 \(D(N)\)?** 使用整除分块将时间复杂度优化至 \(O(\sqrt{N})\),适用于 \(N \leq 10^{12}\) 的规模[^1]。 2. **整除分块的原理是什么?** 利用 \(\left\lfloor \frac{N}{k} \right\rfloor\) 的取值分段,每块右边界为 \(j = \left\lfloor \frac{N}{\left\lfloor \frac{N}{i} \right\rfloor} \right\rfloor\),块内求和可快速计算[^2]。 3. **模运算下如何计算除法和乘法逆元?** 对质数模数 \(P\),数 \(a\) 的逆元 \(a^{-1}\) 满足 \(a \cdot a^{-1} \equiv 1 \pmod{P}\),可用费马小定理计算(如 \(2^{-1} \equiv 499122177 \pmod{998244353}\))[^3]。 4. **类似问题如何应用整除分块?** 适用于求和式 \(\sum_{k=1}^{N} f(\left\lfloor \frac{N}{k} \right\rfloor)\),如莫比乌斯反演、欧拉函数求和等[^4]。 [^1]: 约数个数和的高效计算 [^2]: 整除分块原理 [^3]: 模逆元计算方法 [^4]: 整除分块的应用场景
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值