ACM - 数学 练习题
- 一、数论
- 1、分解质因数 :AcWing 197. 阶乘分解
- 2、求约数个数
- (1)AcWing 1294. 樱花 (求 n!约数个数之和)
- (2)AcWing 198. 反素数 (求 1 ~ N 中约数最多的数)
- (3)AcWing 200. Hankson的趣味题(gcd * lcm == 两数之积)
- 3、欧拉函数
- (1)AcWing 201. 可见的点 :求有多少组互质的点
- (2)AcWing 220. 最大公约数 :N内gcd为素数的数对数量
- 4、同余
- (1)AcWing 222. 青蛙的约会 :求一个未知数的同余方程
- (2)AcWing 202. 最幸运的数字:至少x个连续的8能整除L
- 5、
一、数论
1、分解质因数 :AcWing 197. 阶乘分解
原题链接:https://www.acwing.com/problem/content/199/
/*
先预处理素数,因为所有质因子只可能在素数集中。
再用 while ( n / 素数a ) 反求 n 中最多只能有多少个因子是 a。
*/
#include<iostream>
using namespace std;
const int N = 1000010;
int primes[N], cnt = 0;
bool st[N];
int ans[N];
void init() {
int n = N - 6;
for (int i = 2; i <= n; ++ i) {
if (!st[i]) primes[cnt ++] = i;
for (int j = 0; primes[j] <= n / i; ++ j) {
st[primes[j] * i] = true;
if (i % primes[j] == 0) break;
}
}
}
int main() {
int n;
cin >> n;
init();
//统计数量
for (int i = 0; i < cnt; ++ i) {
int j = n;
while (j >= primes[i]) {
ans[primes[i]] += j / primes[i];
j /= primes[i];
}
}
//输出答案
for (int i = 2; i <= n; ++ i) {
if (ans[i]) printf("%d %d\n", i, ans[i]);
}
if (n == 1) cout << "1 1" << endl;
return 0;
}
2、求约数个数
(1)AcWing 1294. 樱花 (求 n!约数个数之和)
原题链接:https://www.acwing.com/problem/content/description/1296/
思路
因为 1 / x 和 1 / y 都小于 1 / n!,所以 x 和 y 都大于 n!。
故而在 x 是正整数的前提下,需要(x - n!)是 n!^ 2 的约数才可行。
而每一个(x - n!)中 x 只对应一个取值,相应的 y 也有唯一的取值。
所以题目就转换成 n!^ 2 有多少个约数。
借用分解质因数的做法,先预处理素数,因为所有质因子只可能在素数集中,再用 while ( n / 素数a ) 反求 n 中最多只能有多少个因子是 a,最后记得求模求解即可。
#include<iostream>
using namespace std;
const int N = 1000010, modd = 1e9 + 7;
#define ll long long
//ans[j] 对应n!中有多少个素数j
ll ans[N], primes[N], cnt = 0;
bool st[N];
//预处理素数
void init() {
int n = N - 6;
for (int i = 2; i <= n; ++ i) {
if (!st[i]) primes[cnt ++] = i;
for (int j = 0; primes[j] <= n / i; ++ j) {
st[primes[j] * i] = true;
if (i % primes[j] == 0) break;
}
}
}
int main() {
int n;
cin >> n;
init();
//求个数
for (int i = 0; i < cnt; ++ i) {
int j = primes[i];
if (j > n) break;
int m = n;
while (m > 0) {
ans[j] += m / j;
ans[j] %= modd;
m /= j;
}
}
//求结果
ll res = 1;
for (int i = 2; i <= n; ++ i) {
if (ans[i]) {
res *= (ans[i] * 2 + 1);
res %= modd;
}
}
cout << res;
return 0;
}
(2)AcWing 198. 反素数 (求 1 ~ N 中约数最多的数)
原题链接:https://www.acwing.com/problem/content/200/
/*
由约数个数定理可知,约数个数只与指数有关,N 有上限的前提下,底数越小,指数的空间才越大。
所以可以从2, 3, 5, 7, 11, 13, 17, 19, 23, 29
(不用很多素数,因为单是这些数的一次方相乘就超过2e9)去爆搜。
由于要指数之积最大,所以指数的大小应当是单调不增的,这样才越慢接近N。
*/
#include<iostream>
using namespace std;
#define ll long long
ll n, ans = 1, res = 1;
ll a[] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29};
ll nums[15][40];
ll flag = 2e9;
void dfs(int idx, int cnt, ll temp, ll num) {
if (temp > ans || (temp == ans && num < res)) {
ans = temp;
res = num;
}
for (int i = cnt; i >= 0; -- i) {
if (nums[idx][i] && num * nums[idx][i] <= n) {
dfs(idx + 1, i, temp * (i + 1), num * nums[idx][i]);
}
}
}
int main () {
cin >> n;
// cout << 2L * 3 * 5 * 7 * 9 * 11 * 13 * 17 * 19 * 23 << endl;
for (int i = 0; i <= 9; ++ i) {
for (int j = 0; j <= 31; ++ j) {
if (j == 0) nums[i][j] = 1;
else {
nums[i][j] = nums[i][j - 1] * a[i];
}
if (nums[i][j] > flag) break;
}
}
dfs(0, 30, 1, 1);
cout << res;
}
(3)AcWing 200. Hankson的趣味题(gcd * lcm == 两数之积)
原题链接:https://www.acwing.com/problem/content/description/202/
/*
由x和a0的最大公约数是a1,并不能获得x的上限。
但是x一定是b1的约数,而b1的约数最多只有1600个。
所以可以在获得b1所有质因子和次数后,dfs枚举b1的约数,逐一判断是否合法。
或者按照下面代码的做法,获得b0的约数,假设该约数是gcd(x, b0),
由最大公约数*最小公倍数 == x * b0获得x的取值
*/
#include<iostream>
#include<vector>
#include<cmath>
#include<unordered_map>
using namespace std;
#define ll long long
const int N = 50000;
ll n, a0, a1, b0, b1;
ll ans = 0;
int primes[N], cnt = 0;
bool st[N];
//num-质因子 cnt-该质因子出现的次数
struct node{
ll num, cnt;
};
//初始化素数表
void init() {
ll m = 50000;
for (int i = 2; i <= m; ++ i) {
if (!st[i]) primes[cnt ++] = i;
for (int j = 0; primes[j] <= m / i; ++ j) {
st[primes[j] * i] = true;
if (i % primes[j] == 0) break;
}
}
}
ll gcd(ll a, ll b) {
return b == 0 ? a : gcd(b, a % b);
}
//爆搜获得约数
void dfs(vector<node> v, int idx, ll num, unordered_map<ll, bool> book) {
if (idx == v.size()) {
ll x = num * b1 / b0;
if (book.find(x) == book.end() && gcd(x, a0) == a1 && gcd(x, b0) == num && x * b0 == b1 * num) {
++ ans;
book[x] = true;
}
return;
}
for (int i = 0; i <= v[idx].cnt; ++ i) {
dfs(v, idx + 1, num * pow(v[idx].num, i), book);
}
}
void solve() {
vector<node> nums;
unordered_map<ll, bool> book;
ll b = b0;
//获得质因子和次数
for (int i = 0; i < cnt; ++ i) {
ll c = 0;
while (b % primes[i] == 0) {
++ c;
b /= primes[i];
}
if(c > 0) {
nums.push_back({primes[i], c});
}
}
if (b > 1) nums.push_back({b, 1});
dfs(nums, 0, 1, book);
}
int main () {
cin >> n;
init();
while (n --) {
cin >> a0 >> a1 >> b0 >> b1;
ans = 0;
solve();
cout << ans << endl;
}
}
3、欧拉函数
(1)AcWing 201. 可见的点 :求有多少组互质的点
原题链接:https://www.acwing.com/problem/content/203/
/*
本题抽象出来就是找所有第一象限过原点且斜率不同的直线有多少条。
设直线穿过点 x0、y0,由 y = kx 可得 k = y0 / x0。
假如 x0 和 y0 不互质,设他们存在大于 1 的公因数 d,那么点(x0 / d, y0 / d)也穿过该直线。
故而本题其实就是求解有多少组不同的(x,y)互质 ==》欧拉函数
*/
#include<iostream>
using namespace std;
const int N = 1010;
int primes[N], cnt = 0;
int e[N];
bool st[N];
void init() {
int n = N - 5;
e[1] = 1;
for (int i = 2; i <= n; ++ i) {
if (!st[i]) {
primes[cnt ++] = i;
e[i] = i - 1;
}
for (int j = 0; primes[j] <= n / i; ++ j) {
st[primes[j] * i] = true;
if (i % primes[j] == 0) {
e[primes[j] * i] = primes[j] * e[i];
break;
}
e[primes[j] * i] = e[i] * (primes[j] - 1);
}
}
}
int main() {
int t;
cin >> t;
init();
for (int i = 1; i <= t; ++ i) {
int n;
cin >> n;
int ans = 1;
for (int j = 1; j <= n; ++ j) ans += e[j] * 2; // 关于 y = x 对称
cout << i << " " << n << " " << ans << endl;
}
}
(2)AcWing 220. 最大公约数 :N内gcd为素数的数对数量
原题链接:https://www.acwing.com/problem/content/222/
/*
假设gcd(x, y) = d (d是素数),那么显然 gcd(x/d, y/d) = 1。
这意味着 x/d 和 y/d 是互质的。
据此,我们可以枚举 n 内的素数,求 n/d 范围所有能互质的对子的总数量。
受上一题(AcWing 201. 可见的点)的启发,我们可以预设 x/d <= y/d,先求对角线的一半,再翻倍即可。
res += 2 * sum[n / p] - 1; 是因为在乘2运算的时候对角线上的e[1]会被算两次,所以需要减1。
*/
#include<iostream>
using namespace std;
const int N = 10000010;
#define ll long long
int n;
int e[N],primes[N], cnt = 0;
bool st[N];
ll sum[N]; //欧拉函数的前缀和
void init() {
e[1] = 1;
for (int i = 2; i <= n; ++ i) {
if (!st[i]) {
primes[cnt ++] = i;
e[i] = i - 1;
}
for (int j = 0; primes[j] <= n / i; ++ j) {
st[primes[j] * i] = true;
if (i % primes[j] == 0) {
e[primes[j] * i] = primes[j] * e[i];
break;
}
e[primes[j] * i] = e[i] * (primes[j] - 1);
}
}
for (int i = 1; i <= n; ++ i) sum[i] = sum[i - 1] + e[i];
}
int main() {
cin >> n;
init();
ll res = 0;
for (int i = 0; i < cnt; ++ i) {
int p = primes[i];
res += 2 * sum[n / p] - 1;
}
cout << res;
return 0;
}
4、同余
(1)AcWing 222. 青蛙的约会 :求一个未知数的同余方程
原题链接:https://www.acwing.com/problem/content/224/
/*
假设青蛙跳了 p 次,一共走了 q 个整圈,那么 p * (m - n) - q * l = y - x。
由于只有p、q是未知数,所以直接用exgcd求即可。
注意 d | (y - x) 才有解,而且最终跳的次数 p 需要乘上 (y - x) / d 才是方程的一组解。
最后再对 l / gcd 取模求最小整数解即可ac。
*/
#include<iostream>
using namespace std;
#define ll long long
ll exgcd(ll a, ll b, ll &x, ll &y) {
if (b == 0) {
x = 1;
y = 0;
return a;
}
ll d = exgcd(b, a % b, y, x);
y -= a / b * x;
return d;
}
int main() {
ll x, y, m, n, l, a, b;
cin >> x >> y >> m >> n >> l;
ll d = exgcd(m - n, l, a, b);
if ((y - x) % d == 0) {
a *= ((y - x) / d);
ll u = abs(l / d); //注意求绝对值
cout << (a % u + u) % u << endl;
}
else cout << "Impossible";
return 0;
}
(2)AcWing 202. 最幸运的数字:至少x个连续的8能整除L
原题链接:https://www.acwing.com/problem/content/description/204/
思路
① 使用欧拉定理时,10 和 c 互质的证明
把同余方程写成 10 ^ x + p * c = 1 的形式(p为未知数),假如 10 和 c 不互质,那么至少可以对等式左边提取出一个公因子 d,即: d * (10 ^ x / d + p * c / d)= 1.
很明显括号内的值不为0,当把 d 移到等式右边时,1 / d 不能整除且不能和左边相等,故而 d 必然为 1,即 10 和 c 一定互质。
② 同余方程的乘法法则
假如 a 同余 b(mod c),x 同余 y(mod c),那么 a * x 同余 b * y (mod c)。
= = = = =》
综上,我们可以发现先求出常数 c = 9 * L / gcd(L, 8),再求和 c 互质的数的个数 phi,再用快速幂check一遍phi的除数中能满足同余方程的最小的除数。
当然快速幂过程有10次方 * 10次方,所以需要在乘法过程中用一下龟速乘。
代码
//推导过程太过摧枯拉朽
#include<iostream>
#include<cmath>
using namespace std;
#define ll long long
ll l, phi, c;
//求a和b的最大公约数
ll gcd(ll a, ll b) {
return b ? gcd(b, a % b) : a;
}
//求1~c中有多少个和c互质的数
ll eul(ll c) {
ll res = c;
ll len = sqrt(c);
for (ll i = 2; i <= len; ++ i) {
if (c % i == 0) {
while (c % i == 0) {
c /= i;
}
res /= i;
res *= (i - 1);
}
}
if (c != 1) {
res /= c;
res *= (c - 1);
}
return res;
}
//龟速乘:求(a * b)% p
ll qmul(ll a, ll b, ll p) {
ll res = 0, t = a;
while (b) {
if (b & 1) res = (res + t) % p;
b >>= 1;
t = (t + t) % p;
}
return res;
}
//快速幂:求10的k次方mod c
ll qmi(ll a, ll k, ll p) {
ll res = 1, t = a;
while (k) {
if (k & 1) res = qmul(res, t, p);
k >>= 1;
t = qmul(t, t, p);
}
return res == 1;
}
//check(k):判断10的k次方模c是不是等于1,即判断k是不是一组解
bool check(ll k) {
return qmi(10, k, c) == 1;
}
int main() {
int k = 1;
while (cin >> l, l) {
ll ans = 0x3f3f3f3f3f;
c = 9 * l / gcd(l, 8);
if (c % 2 == 0 || c % 5 == 0) ans = 0;
phi = eul(c);
for (ll i = 1; i <= phi / i; ++ i) {
if (phi % i == 0) {
if (check(i)) ans = min(ans, i);
if (check(phi / i)) ans = min(ans, phi / i);
}
}
printf("Case %d: %lld\n", k, ans);
++ k;
}
return 0;
}