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,那么复杂度会爆掉,思考如何计算这个式子。
!!! 我们可以把式子拆成两半
-
式子的第一部分:显然等于 $ 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;
}

浙公网安备 33010602011771号