What comes after, tiny fracture|

落花月朦胧

园龄:3年6个月粉丝:14关注:10

从头开始学数论(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 的范围, 但是 rl107 , 只用开 107 的数组, 只考虑 l, r 范围里的数。

类似的, 用模的方式把范围缩小到 107 的规模, 用普通的筛就好了

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, 求这个范围里相邻质数的最大值和最小值。

分析

相邻质数用线性复杂度就可以解决,所以本题的难点是求质数。

直接预处理质数会导致时间复杂度爆炸, 用筛法优化空间复杂度又会爆炸。

我们可以假设用筛法, 如何优化空间复杂度?

可以想到不用求全部的质数, 因为RL1000000, 所以我们利用这 106 的空间只处理 R, L 以内的质数就可以了, 而用离散化的思路把空间复杂度再优化。

对于本题可以先预处理出 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=i=1nkmodai

可以发现数据中可能会有 k 小于 ai 的情况, 在这种情况下答案就是 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) 的循环, 我们需要想办法优化这个循环。

用仅有的数论知识可以知道

kmodi=ki(k/i)

所求就是

i=1nki(k/i)=kni=1ni(k/i)

及现在要求的是

i=1ni(k/i)

k/i=p 成立, 因为向下取整, 所以 i 一定会有多个, 并且是单调递减的。

在实际表的过程中可以验证这个规律。

可以用分块的思想把这个数列分成多个块

同时对于每个块都有
i=1ni(k/i)=i=1nii=1n(k/i)

l,r 表示这个块第一个和最后一个。

用代码和求和公式表示
i=1nii=1n(k/i)
即为

(k / i) * (l + r) * (r - l + 1) >> 1;

最后就是对 l, r 的求解

假设 r已经求出, 用l 表示下一个块就是 r+1.

对于每个 r, 设 x=k/i(其中 i<n), 由于向下取整, 所以 x=k/ik/i , 即 xk/i, 所以就有 ik/x, 即 ik/(k/i)

在数轴上表示:

在这个解集中最右边的数是 k/(k/l), 所以每个块的 r, 都是 k/(k/l)

又因为 ln, 和 i 等价, x 也为定值, 所以在代码实现中全部用 l, 代替 i。 也可以用任意 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=x1b1×x2b2×xpbp

其中 x 都是质数。

意思就是说任意一个数都可以分解成多个质数的乘积。

考虑合数一定可以被分解成别的质数的乘。

质数 a 虽然也可以被分解成 1×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 的趣味题

给出 a0,a1,b0,b1 求下面的方程解的个数。

{gcd(x,a0)=a1lcm(x,b0)=b1

可以考虑 xb1 的因数, 可以用 O(b1) 的时间枚举这个因数,在判断是否 合法。时间复杂度 O(Tb1log2b1)

显然这个做法只可以得到 50 分。

考虑因数基本都是成对出现的 ,可以用 b1 的时间枚举它一半的因数,一次可以枚举两个 i,b1/i 。时间复杂度 O(Tb1log2b1) ,就可以过了。

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)×φ(k)=φ(i×k)

    证明: 设 nm 互质, 设 mam 个因数, nan 个因数:

    φ(n)×φ(m)=n×m×i=1ampi1pi×i=1anpi1pi=(n×m)×i=1am+anpi1pi=φ(n×m)

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

  4. 如果 i=pk (p为质数,k1) ,就有 φ(pk)=pkpk1=pk(1p1)=pk(11p)=i=1kpi1pi, 对于 φ(pk) ,一共有 pk 个数可能被选为与 pk 互质的数,它们就是 1 到 pk 的所有数。可以想到,质数 p 的倍数的数应有 pk1 个,就是 p×1,p×2,p×3,p×pk1,减去这 pk1 个数就是这个欧拉函数的值,可以发现, 当 k=1 时满足第 3 条性质, 当 k=0 时满足第 1条性质。

设原数是 n, 通过唯一分解得 n=p1k1×p2k2×pmkm

通过第二条性质 φ(n)=φ(p1k1)×φ(p2k2)φ(pmkm)

通过第四条性质 φ(n)=p1k1×p2k2pmkm×(11p1)×(11p2)×(11pm)

通过唯一分解可得 φ(n)=n×(11p1)×(11p2)×(11pm)=n×i=1mpi1pi

上面的结果就是化简的最终结果。

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;
}

对于筛法:

pn 的最小质因数, n=np,那么 n 就是通过 n 筛掉的。

再利用如下性质实现线性筛求欧拉函数。

  • imodx=0: 其中第一步用朴素的式子替换欧拉函数

    φ(n)=n×i=1spi1pi=pi×n×i=1spi1pi=pi×φ(n)

  • imodx0。 那么: φ(i×k)=φ(i)×φ(k)=(i1)×φ(k)

「BZOJ2818」gcd

求一对数 x, y, 要求:

  • 1x,yn
  • gcd(x,y) 是素数

给出 n, 求满足条件的 x, y 的对数。

化简一下第二个条件:

gcd(x,y)质数, 即 gcd(a×p,b×p)=p, 其中 x=a×p,y=b×p, p是一个质数。

显然有 a,b 互质。

如果有 a 小于 b, 答案明显就是 φ(b) 的值。

同时为了满足第一个条件, a×pn,b×pn 就有 a,bnp

综合一下答案, 就是 i=1npφ(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 多记录的一次。

高斯消元

就是求多元一次方程组。

[a1,1x1+a1,2x2+a1,3x3+a1,nxn=b1a2,1x1+a2,2x2+a2,3x3+a2,nxn=b2a3,1x1+a3,2x2+a3,3x3+a3,nxn=b3]

高斯消元可以得到 x1xn 的答案

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

[a1,1,a1,2,a1,3,a1,n,|b1a2,1,a2,2,a2,3,a2,n,|b2a3,1,a3,2,a3,3,a3,n,|b3]

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

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

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

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

[1,0,00,0=y10,1,00,0=y20,0,00,1=yn]

明显各个答案就是后面的常数 y1,y2yn

思考无解的情况:

  • 没有解
  • 有无数组解。

对于第一种情况,当系数为 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]);

本文作者:Falling-flowers 的 blog

本文链接:https://www.cnblogs.com/falling-flowers/p/15874198.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   落花月朦胧  阅读(71)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起