从头开始学数论(1)
从头开始学数论(1)
筛法
阶乘分解
题意是把一个数的阶乘通过分解质因数的方式分解出来并输出。
质因数分解自然要求质数, 对于本题直接用欧拉筛就可以了。
for (int i = 2; i <= n; i++) {
if (!prime[i]) prime[++prime[0]] = i;
for (int j = 1; j <= prime[0] && i * prime[j] <= n; j++) {
prime[i * prime[j]] = 1;
if (i % prime[j] == 0) break;
}
}
for (int i = 1; i <= prime[0]; i++) {
int t = prime[i], ans = 0;
for (int j = n; j; j /= t) ans += j / t;
printf("%d %d\n", t, ans);
}
无平方因子数
题目大意就是求一个区间里有多少个没有平方因子的数的个数。
虽然 \(l\), \(r\) 的大小大于了 \(int\) 的范围, 但是 \(r - l \le 10^ 7\) , 只用开 \(10 ^ 7\) 的数组, 只考虑 \(l\), \(r\) 范围里的数。
类似的, 用模的方式把范围缩小到 \(10 ^ 7\) 的规模, 用普通的筛就好了
inline ll getans(ll l, ll r) {
ll ans = 0;
for (int i = 2; i <= (sqrt)(r + 0.5); i++)
for (int j = l / i / i; j * i * i <= r; j++)
if (j * i * i >= l)
vis[j * i * i % (r - l + 1) + 1] = 1;
for (int i = 1; i <= r - l + 1; i++) ans += (!vis[i]);
return ans;
}
「POJ2689」Prime Distance
题目大意
给出一个范围 \(L, R\), 求这个范围里相邻质数的最大值和最小值。
分析
相邻质数用线性复杂度就可以解决,所以本题的难点是求质数。
直接预处理质数会导致时间复杂度爆炸, 用筛法优化空间复杂度又会爆炸。
我们可以假设用筛法, 如何优化空间复杂度?
可以想到不用求全部的质数, 因为\(R-L\leq1000000\), 所以我们利用这 \(10^6\) 的空间只处理 \(R\), \(L\) 以内的质数就可以了, 而用离散化的思路把空间复杂度再优化。
对于本题可以先预处理出 \(\sqrt{n}\) 的质数, 再根据 \(L\), \(R\)的范围处理这个范围里的质数, 暴力枚举最大值和最小值就好了。
还有一个需要特判,如果 \(L\) 为1, 不会筛掉\(1\), 加一个排除1的特判就好了。
for (int i = 2; i <= M; i++) {if (!vis[i]) {prime[++prime[0]] = i;for (int j = 1; j * i <= M; j++) vis[j * i] = 1;}}
while (~scanf("%d%d", &l, &r)) {
memset(vis, 0, sizeof vis);
for (int i = 1; i <= prime[0]; i++)
for (int j = l / prime[i]; j <= r / prime[i]; j++)
if (j > 1) vis[prime[i] * j - l] = 1;
int siz = 0;
if (l == 1) vis[0] = 0;
for (int i = l; i <= r; i++) if (!vis[i - l]) ans[++siz] = i;
int ans1 = INF, ans2 = -INF;
int a, b, c, d;
for (int i = 1; i < siz; i++) {
if (ans[i] == 1) continue;
int k = ans[i + 1] - ans[i];
if (k < ans1) ans1 = k, b = ans[i + 1], a = ans[i];
if (k > ans2) ans2 = k, d = ans[i + 1], c = ans[i];
}
if (ans1 == INF || ans2 == -INF) printf("There are no adjacent primes.\n");
else printf("%d,%d are closest, %d,%d are most distant.\n", a, b, c, d);
}
「BZOJ1257」余数之和
题目就是要求
\(ans = \sum_{i=1}^n {k\mod a_i}\)
可以发现数据中可能会有 \(k\) 小于 \(a_i\) 的情况, 在这种情况下答案就是 \(k\), 于是我们优雅的暴力就出来了。
cin >> n >> k;
if (n > k) ans += (ll)((n - k) * k), n = k;
for (int i = 1; i <= n; i++) ans += (ll)(k % i);
cout << ans << endl;
在实际测试中这个代码可以得到 \(81\) 的高分。
这个暴力的时间复杂度高的原因就是那个 \(O(n)\) 的循环, 我们需要想办法优化这个循环。
用仅有的数论知识可以知道
\(k \mod i = k - i * (k / i)\)
所求就是
\(\sum_{i =1} ^{n}k - i * (k / i) = k * n - \sum_{i = 1} ^ {n} i * (k / i)\)
及现在要求的是
若 \(k / i = p\) 成立, 因为向下取整, 所以 \(i\) 一定会有多个, 并且是单调递减的。
在实际表的过程中可以验证这个规律。
可以用分块的思想把这个数列分成多个块
同时对于每个块都有
\(\sum_{i = 1} ^ ni * (k / i)= \sum_{i = 1} ^n i * \sum_{i = 1} ^ n (k / i)\)
用 \(l, r\) 表示这个块第一个和最后一个。
用代码和求和公式表示
\(\sum_{i = 1} ^ n i * \sum_{i = 1} ^ n (k / i)\)
即为
(k / i) * (l + r) * (r - l + 1) >> 1;
最后就是对 \(l\), \(r\) 的求解
假设 \(r\)已经求出, 用\(l\) 表示下一个块就是 \(r + 1\).
对于每个 \(r\), 设 \(x = \lfloor k / i\rfloor\)(其中 \(i < n\)), 由于向下取整, 所以 \(x = \lfloor k / i\rfloor \leq k / i\) , 即 \(x \le k / i\), 所以就有 \(i \le k / x\), 即 \(i \le k / (k / i)\)。
在数轴上表示:

在这个解集中最右边的数是 \(k / (k/l)\), 所以每个块的 \(r\), 都是 \(k / (k / l)\)
又因为 \(l \in n\), 和 \(i\) 等价, \(x\) 也为定值, 所以在代码实现中全部用 \(l\), 代替 \(i\)。 也可以用任意 \(\in n\) 的数来表示
cin >> n >> k;
if (n > k) ans += (ll)((n - k) * k), n = k;
ans += k * n;
for (int l = 1, r = 0; l <= n; l = r + 1) {
r = min(n, k / (k / l));
ans -= (k / l) * (l + r) * (r - l + 1) >> 1;
}
cout << ans << endl;
CF776B Sherlock and his girlfriend
思维题, 但是不难。
要求给不同数字分配一个颜色(另一个数字), 如果一个数是另一个的质因数, 那颜色就不能染一样的, 要求最小化颜色数量并输出最后的方案。
考虑不同颜色之间 数的关系, 根据唯一分解的定理可知
其中 \(x\) 都是质数。
意思就是说任意一个数都可以分解成多个质数的乘积。
考虑合数一定可以被分解成别的质数的乘。
质数 \(a\) 虽然也可以被分解成 \(1 \times a\), 但是因为 1 不是质数 , 且 \(a\) 是它本身。
合数既然一定可以被分解, 质数一定不能被其他质数分解,要使颜色不同,那就可以合数一个颜色, 质数一个颜色。
保证了题目条件并且是最少的。
cin >> n; vis[0] = vis[1] = 1;
for (int i = 2; i <= N; i++) {
if (!vis[i]) prime[++prime[0]] = i;
for (int j = 1; j <= prime[0] && i * prime[j] <= N; j++) {
vis[prime[j] * i] = 1;
if (i % prime[j] == 0) break;
}
}
n >= 3 ? cout << 2 << endl : cout << 1 << endl;
for (int i = 1; i <= n; i++)
vis[i + 1] ? cout << "2 " : cout << "1 ";
「NOIP2009」Hankson 的趣味题
给出 \(a_0, a_1, b_0, b_1\) 求下面的方程解的个数。
可以考虑 \(x\) 是 \(b_1\) 的因数, 可以用 \(O(b_1)\) 的时间枚举这个因数,在判断是否 合法。时间复杂度 \(O(T b_1log_2b_1)\)
显然这个做法只可以得到 50 分。
考虑因数基本都是成对出现的 ,可以用 \(\sqrt{b_1}\) 的时间枚举它一半的因数,一次可以枚举两个 \(i, b_1 / i\) 。时间复杂度 \(O(T\sqrt{b1}log_2b1)\) ,就可以过了。
function <void()> solve = [&]() {
int ans = 0;
cin >> a0 >> a1 >> b0 >> b1;
// 用 sqrt(b1) 的时间判断完因数。
for (int i = 1; i <= int(sqrt(b1)); i++) {
// i 是否是 b1的因数
if (b1 % i) continue;
ans += check(i) + (b1 / i != i && check(b1 / i));
}
cout << ans << endl;
};
欧拉函数
欧拉函数\(φ(n)\) 是小于或等于 \(n\) 的正整数中与 \(n\) 互质的数的个数。
——如何求 ? ——利用朴素或筛法
对于朴素还是推式子:
先列出欧拉函数的几个性质:
-
\(φ(1) = 1\)
-
它是一个积性函数, 即 \(φ(i) \times φ(k) = φ(i \times k)\)
证明: 设 \(n\),\(m\) 互质, 设 \(m\) 有 \(a_m\) 个因数, \(n\) 有 \(a_n\) 个因数:
\(φ(n) \timesφ(m)=n\times m\times \prod_{i=1}^{a_m}\frac{p_i - 1}{p_i} \times\prod_{i=1}^{a_n}\frac{p_i - 1}{p_i}=(n\times m) \times\prod_{i=1}^{a_m+a_n}\frac{p_i-1}{p_i}=φ(n\times m)\)
-
如果 \(i\) 是质数, 就有 \(φ(i) = i - 1\) 。因为质数和其他除自己之外的数都互质(定义认为 1也与 \(i\) 互质)。
-
如果 \(i = p^k\) (\(p\)为质数,\(k\ge1\)) ,就有 \(φ(p^k) = p^k - p^{k - 1}=p^k(1-p^{-1}) = p^k(1-\frac{1}{p})=\prod_{i=1}^k\frac{p_i - 1}{p_i}\), 对于 \(φ(p^k)\) ,一共有 \(p^k\) 个数可能被选为与 \(p^k\) 互质的数,它们就是 1 到 \(p^k\) 的所有数。可以想到,质数 \(p\) 的倍数的数应有 \(p^{k -1}\) 个,就是 \(p\times1,p\times2,p\times3,\dots p\times p^{k - 1}\),减去这 \(p^{k - 1}\) 个数就是这个欧拉函数的值,可以发现, 当 \(k =1\) 时满足第 3 条性质, 当 \(k=0\) 时满足第 1条性质。
设原数是 \(n\), 通过唯一分解得 $n = {p_1}^{k_1} \times{p_2}^{k_2} \times \dots{p_m}^{k_m} $。
通过第二条性质 \(φ(n)=φ({p_1}^{k_1})\timesφ({p_2}^{k_2})\dotsφ({p_m}^{k_m})\)
通过第四条性质 \(φ(n) = {p_1}^{k_1}\times{p_2}^{k_2}\dots{p_m}^{k_m}\times(1-\frac{1}{p_1})\times(1-\frac{1}{p_2})\dots\times(1-\frac{1}{p_m})\)
通过唯一分解可得 \(φ(n) = n \times(1-\frac{1}{p_1})\times(1-\frac{1}{p_2})\dots\times(1-\frac{1}{p_m})=n\times\prod_{i=1}^m\frac{p_i - 1}{p_i}\)
上面的结果就是化简的最终结果。
using i64 = long long;
function <i64(i64)> oula = [&] (i64 x) {
i64 t = x;
for (i64 i = 2; i <= x / i; i++) if (x % i == 0) {
t = t / i * (i - 1);
while (x % i == 0) x /=i;
}
if (x > 1) t = t / x * (x - 1);
return t;
};
while (true) {
cin >> x;
if (x == 0) return 0;
cout << oula(x) << endl;
}
对于筛法:
设 \(p\) 是 \(n\) 的最小质因数, \(n'=\frac{n}{p}\),那么 \(n\) 就是通过 \(n'\) 筛掉的。
再利用如下性质实现线性筛求欧拉函数。
-
\(i \mod x =0\): 其中第一步用朴素的式子替换欧拉函数
\[φ(n) = n \times \prod_{i=1}^s\frac{p_i - 1}{p_i}=p_i\times n'\times\prod_{i=1}^s\frac{p_i - 1}{p_i}=p_i\timesφ(n') \] -
\(i \mod x \neq0\)。 那么: \(φ(i \times k) = φ(i) \timesφ(k) = (i - 1) \timesφ(k)\)
「BZOJ2818」gcd
求一对数 \(x\), \(y\), 要求:
- \(1\le x,y\le n\)
- \(\gcd(x,y)\) 是素数
给出 \(n\), 求满足条件的 \(x\), \(y\) 的对数。
化简一下第二个条件:
\(\gcd(x, y) 是 \texttt{质数}\), 即 \(\gcd(a \times p, b \times p) = p\), 其中 \(x = a \times p, y = b \times p\), \(p\)是一个质数。
显然有 \(a, b\) 互质。
如果有 \(a\) 小于 \(b\), 答案明显就是 \(φ(b)\) 的值。
同时为了满足第一个条件, \(a \times p \leq n, b \times p\leq n\) 就有 $a, b \le \frac{n}{p} $
综合一下答案, 就是 \(\sum_{i = 1} ^ {\frac{n}{p}} φ(i)\) 。
显然,只用求 \(φ(i)\) 本题就解决了。
ou[1] = 1;
for (int i = 2; i <= n; i++) {
if (!v[i]) {
prime[++prime[0]] = i;
ou[i] = i - 1;
}
for (int j = 1; j <= prime[0] && i * prime[j] <= n; j++) {
v[i * prime[j]] = 1;
if (i % prime[j] == 0) {ou[i * prime[j]] = ou[i] * prime[j];break;}
if (i % prime[j] != 0) ou[i * prime[j]] = ou[i] * (prime[j] - 1);
}
}
for (int i = 1; i <= n; i++) sum[i] = sum[i - 1] + ou[i];
for (int i = 1; i <= prime[0]; i++) ans += sum[n / prime[i]] * 2 - 1;
printf("%lld\n", ans);
用前缀和优化,又因为数字对反过来也是一组解,所以要乘 2,还要减去 \(1, 1\) 多记录的一次。
高斯消元
就是求多元一次方程组。
高斯消元可以得到 \(x_1\) 到 \(x_n\) 的答案
可以直记录方程组各个未知数的系数和等式右边的常数如下:
大概就是这个形式。。。。左边的系数+右边的常数就是增广矩阵
高斯消元下面通过三种操作把方程简化成简化接替式矩阵。
- 把一行的数全部乘一个数 \(a(a\neq0)\)。
- 用一行减去另外一行。
- 将某两行的数交换。
最后应该把矩阵化简为下面这种形式, 这样才可以求解。
明显各个答案就是后面的常数 \(y_1, y_2 \dots y_n\) 。
思考无解的情况:
- 没有解
- 有无数组解。
对于第一种情况,当系数为 0 时,常数不为零。
对于第二种情况, 消 \(x\) 的元把 \(y\) 消掉了,说明 \(y\) 与原方程没有关系,\(x\) 叫做主元, \(y\) 叫做自由元。
对于代码
for (int i = 1; i <= n; i++) {
int f = 1;
int m = 0, id = 0;
for (int j = i; j <= n; j++)
if (abs(a[j][i]) > m) f = 0, m = abs(a[j][i]), id = j;
if (f) return printf("No Solution\n"), 0;
for (int j = 1; j <= n + 1; j++) swap(a[i][j], a[id][j]);
for (int j = 1; j <= n; j++) {
if (i != j) {
double b = a[j][i] / a[i][i];
for (int k = i; k <= n + 1; k++)
a[j][k] -= a[i][k] * b;
}
}
}
for (int i = 1; i <= n; i++)
printf("%.2f\n", a[i][n + 1] / a[i][i]);

浙公网安备 33010602011771号