从头开始学数论(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)\)

及现在要求的是

\[\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

思维题, 但是不难。

要求给不同数字分配一个颜色(另一个数字), 如果一个数是另一个的质因数, 那颜色就不能染一样的, 要求最小化颜色数量并输出最后的方案。

考虑不同颜色之间 数的关系, 根据唯一分解的定理可知

\[a = x_1^{b_1} \times x_2^{b_2} \times \dots x_p^{b_p} \]

其中 \(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\) 求下面的方程解的个数。

\[\left\{ \begin{array}{**lr**} \gcd(x, a_0) = a_1\\ lcm(x, b_0) = b_1\\ \end{array}\right. \]

可以考虑 \(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) = 1\)

  2. 它是一个积性函数, 即 \(φ(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)\)

  3. 如果 \(i\) 是质数, 就有 \(φ(i) = i - 1\) 。因为质数和其他除自己之外的数都互质(定义认为 1也与 \(i\) 互质)。

  4. 如果 \(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\) 多记录的一次。

高斯消元

就是求多元一次方程组。

\[ \begin{bmatrix} a_{1,1}x_1 + a_{1,2}x_2 +a_{1,3}x_3 \dots+a_{1,n}x_n = b_1 &\\ a_{2,1}x_1 + a_{2,2}x_2 +a_{2,3}x_3 \dots+a_{2,n}x_n = b_2 &\\ a_{3,1}x_1 + a_{3,2}x_2 +a_{3,3}x_3 \dots+a_{3,n}x_n = b_3 &\\ \end{bmatrix} \]

高斯消元可以得到 \(x_1\)\(x_n\) 的答案

可以直记录方程组各个未知数的系数和等式右边的常数如下:

\[ \begin{bmatrix} a_{1,1}, a_{1,2},a_{1,3} \dots,a_{1,n}, |b_1\\ a_{2,1}, a_{2,2},a_{2,3} \dots,a_{2,n}, |b_2 \\ a_{3,1}, a_{3,2},a_{3,3} \dots,a_{3,n}, |b_3 \\ \end{bmatrix} \]

大概就是这个形式。。。。左边的系数+右边的常数就是增广矩阵

高斯消元下面通过三种操作把方程简化成简化接替式矩阵。

  • 把一行的数全部乘一个数 \(a(a\neq0)\)
  • 用一行减去另外一行。
  • 将某两行的数交换。

最后应该把矩阵化简为下面这种形式, 这样才可以求解。

\[\begin{bmatrix} 1, 0, 0\dots0,0 = y_1\\ 0, 1, 0\dots0,0 = y_2\\ \dots\\ 0, 0, 0\dots0,1 = y_n\\ \end{bmatrix} \]

明显各个答案就是后面的常数 \(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]);
posted @ 2022-02-09 11:13  落花月朦胧  阅读(105)  评论(0)    收藏  举报