AT_abc414_e题解

题目传送门

\(\color{black}{AtCoder 414 E}\)

题面

题面很简单:

给定 \(N\) ,要求找出满足以下条件的 \(a,b,c\) 三个数

  • \(a,b,c \leq N\)
  • \(a,b,c \neq 0\)
  • \(a,b,c\) 两两不同
  • \(a \mod b = c\)

请问有多少种不同的 \(a,b,c\) 方案,两种方案称为不同当且仅当两个方案 \(a,b,c\) 中至少有一个数字不同。

思路

首先我们发现有 \(a\)\(b\) 就一定可以确定 \(c\),所以只需要考虑 \(a\)\(b\) 就可以了。

然后继续观察:发现 \(c\) 如果是 \(0\) 就不符合要求,也就是说 \(a \mod b \neq 0\), 也就相当于 \(a\) 不能是 \(b\) 的倍数。

继续:如果\(a < b\) 那么 \(c\) 就等于 \(a\),必然不符合题目要求。

那么这道题就相当于:

找满足以下条件的 \(a,b\) 的个数

  • \(1 \leq a < b \leq N\)
  • \(a\) 不是 \(b\) 的倍数

接下来就到了思考这个问题的时间了!

我们约定 \(f(i)\) 表示当 \(b = i\)\(a\) 有多少种取法

\(b+1\)\(n\) 这个区间一共有 \(n-b\) 个数字,其中有 \(\left \lfloor \frac{N}{b} \right \rfloor - 1\)\(b\) 的倍数。

所以:\(f(i) = (N - i) - (\left \lfloor \frac{N}{i} \right \rfloor - 1) = N - i + 1 - \left \lfloor \frac{N}{i} \right \rfloor\)

那么显然我们要求的是:\(\displaystyle{\sum_{i=1}^{N} f(i) = \sum_{i=1}^{N} ( N - i + 1 - \left \lfloor \frac{N}{i} \right \rfloor )}\)
可是如果我们直接枚举i,那么复杂度会爆掉,思考如何计算这个式子。

!!! 我们可以把式子拆成两半

\[\sum_{i=1}^{N} ( N - i + 1 - \left \lfloor \frac{N}{i} \right \rfloor ) = \sum_{i=1}^{N} ( N - i + 1 ) - \sum_{i=1}^{N}\left \lfloor \frac{N}{i} \right \rfloor \]

  • 式子的第一部分:显然等于 $ 1 + 2 + ... + N $ 也就等于 $ \frac{N * (N + 1)}{2}$

  • 式子的第二部分:需要通过除法分块的方法来求:
    这里讲一下:
    我们要求\(\displaystyle{\sum_{i=1}^{k}\left \lfloor \frac{k}{i} \right \rfloor}\)
    怎么求呢?

我们不妨先看看当 \(k\) 等于 \(24\) 时,$\left \lfloor \frac{k}{i} \right \rfloor $ 分别等于几 ( $ 1 \leq i \leq k $ )。

$i = $ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
\(\left \lfloor \frac{k}{i} \right \rfloor\) = 24 12 8 6 4 4 3 3 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1

我们发现:有很多数字\(\left \lfloor \frac{k}{i} \right \rfloor\)有连续一段是相同的,那么我们看看每一段中左端的 \(i\) 和有段的 \(i\) 以及 \(\left \lfloor \frac{k}{i} \right \rfloor\) 的联系。

段号
左右的 \(i\) \(1,1\) \(2,2\) \(3,3\) \(4,4\) \(5,6\) \(7,8\) \(9,12\) \(13,24\)
\(\left \lfloor \frac{k}{i} \right \rfloor\) 24 12 8 6 4 3 2 1

我们观察最后一段:右\(i = 24\)是必然的,那么这一段的\(\left \lfloor \frac{k}{i} \right \rfloor\)就一定是$ \left \lfloor \frac{k}{24} \right \rfloor = 1$ 了,那么左\(i = 13\)是怎么来的呢? 我们发现 \(13\) 就等于 \(\left \lfloor \frac{k}{1+1} \right \rfloor + 1 = 12 + 1 = 13\) ,可真的是这样吗?
继续观察倒数第二段:右\(i =\) 最后一段的 左\(i - 1 = 12\)是显然的, 然后\(\left \lfloor \frac{k}{i} \right \rfloor\) 就是\(2\),然后用我们想到的:\(\frac{k}{\left \lfloor \frac{k}{i} \right \rfloor + 1 } + 1\),它等于:\(\left \lfloor \frac{24}{2+1} \right \rfloor + 1 = 8 + 1 = 9\),确实是\(9\)

后面的读者们可以自己在看看,这个结论是对的。

看一下更直观的:

段号
左右的 \(i\) \(\left \lfloor \frac{k}{24+1} \right \rfloor+1=1,2-1=1\) \(\left \lfloor \frac{k}{12+1}\right \rfloor+1=2,3-1=2\) \(\left \lfloor \frac{k}{8+1} \right \rfloor+1=3,4-1=3\) \(\left \lfloor \frac{k}{6+1} \right \rfloor+1=4,5-1=4\) \(\left \lfloor \frac{k}{4+1} \right \rfloor+1=5,7-1=6\) \(\left \lfloor \frac{k}{3+1} \right \rfloor+1=7,9-1=8\) \(\left \lfloor \frac{k}{2+1} \right \rfloor+1=9,13-1=12\) \(\left \lfloor \frac{k}{1+1} \right \rfloor +1=13,24\)
\(\left \lfloor \frac{k}{i} \right \rfloor\) 24 12 8 6 4 3 2 1

那么知道了每一段的值和长度,计算 \(\displaystyle{\sum_{i=1}^{k}\left \lfloor \frac{k}{i} \right \rfloor}\)
就可以直接每一段的值乘长度再累加去做了。

\(k=24\)

段号
左右的 \(i\) \(1,1\) \(2,2\) \(3,3\) \(4,4\) \(5,6\) \(7,8\) \(9,12\) \(13,24\)
\(\left \lfloor \frac{k}{i} \right \rfloor\) 24 12 8 6 4 3 2 1
这一段的总和 \((1-1+1)*24=24\) \((2-2+1)*12=12\) \((3-3+1)*8=8\) \((4-4+1)*6=6\) \((6-5+1)*4=8\) \((8-7+1)*3=6\) \((12-9+1)*2=8\) \((24-13+1)*1=12\)

那么当\(k=24\)时:\(\displaystyle{\sum_{i=1}^{k}\left \lfloor \frac{k}{i} \right \rfloor = 24+12+8+6+8+6+8+12=84}\)

那么这就叫除法分块

让代码更清新:我们可以把左闭右闭得区间改成左开右闭,这样不仅 右\(i\) 从下一段的 左\(i-1\) 变成了 下一段的 左\(i\), 而且计算长度从 右$i - $ 左\(i + 1\) 变成了 右\(i\) - 左\(i\)

代码:

//这里使用的是左开右闭的方法。
long long f(long long n) 
{
    long long s = 0; // 求和
    for (long long i = n; i > 0; )  // 表示当前区间右端
    {
        long long x = n / i; // 找到这个区间的值
        long long l = n / (x + 1); // 找到左端
        s += (i - l) % mod * (x % mod) % mod; // 计算这个区间的和
        s %= mod;
        i = l; // 更新为左端
    }
    return s;
}

那代码就这样写出来了:

#include <iostream>
#define mod 998244353
using namespace std;
long long f(long long n)
{
    long long s = 0;
    for (long long i = n; i > 0; )
    {
        long long x = n / i;
        long long l = n / (x + 1);
        s += (i - l) % mod * x % mod;
        s %= mod;
        i = l;
    }
    return s;
}
int main()
{
    long long n;
    cin >> n;
    cout << (n % mod * ((n + 1) % mod) % mod / 2 % mod - f(n) % mod + mod) % mod << endl; // 其实把%mod都去掉就很好看了。
    return 0;
}

然后我们自信一交!

AC:39 WA:17

怎么会这样!

我们发现 \(\frac{n * (n + 1)}{2}\) 不可以先把 \(n\)\(n+1\) 取模,不然 \(÷ 2\) 会出问题!

那怎么办?

方法1

不能用long long那就用更大的呗。

没错! __int128!

其实没啥好讲的,就是注意要先给__int128 赋成 \(n\) 然后再乘以 \(n+1\) 然后再除以\(2\),最后再 \(\mod 998244353\)

注意:__int128不能直接输出,要转换成 int 或者 long long 输出。

代码:

#include <iostream>
using namespace std;
const long long mod = 998244353;
long long f(long long n)
{
    long long s = 0;
    for (long long i = n; i > 0; )
    {
        long long x = n / i;
        long long l = n / (x + 1);
        s += (i - l) % mod * (x % mod) % mod;
        s %= mod;
        i = l;
    }
    return s;
}
int main()
{
    long long n;
    cin >> n;
    __int128 ff = n; // 先用n赋值
    ff *= (n + 1); // 乘上n+1
    ff /= 2; // 除以2
    ff %= mod; // 取模
    cout << ((long long)ff - f(n) + mod) % mod; // 注意取long long
    return 0;
}

方法2

大数除法取模怎么做!逆元!!!

逆元:把除以一个改为乘上一个数。

\(÷ 2 \mod 998244353\) 就相当于:$ \times (2^{998244353-2}\mod 998244353)$

那么\(2^{998244353-2}\mod 998244353\)可以用快速幂来做。

代码:

#include <iostream>
using namespace std;
const long long mod = 998244353;
long long f(long long n)
{
    long long s = 0;
    for (long long i = n; i > 0; )
    {
        long long x = n / i;
        long long l = n / (x + 1);
        s += (i - l) % mod * (x % mod) % mod;
        s %= mod;
        i = l;
    }
    return s;
}
long long q2(long long n) // 快速幂 原理:a^s = (a*a)^(s/2)
{
    long long s = 1;
    long long a = 2;
    while (n)
    {
        if (n % 2 == 1)
        {
            s *= a;
            s %= mod;
        }
        a *= a;
        a %= mod;
        n /= 2;
    }
    return s;
}
int main()
{
    long long n;
    cin >> n;
    long long inv = q2(mod - 2); // 用快速幂计算2^(mod-2)
    long long ff = n % mod * ((n + 1) % mod) % mod * inv % mod; // 这样就不会出现不能除法了。
    cout << (ff - f(n) + mod) % mod << endl;
    return 0;
}
posted @ 2025-07-19 13:35  MichaelZeng  阅读(37)  评论(0)    收藏  举报