首页 > 代码库 > 2017.8.9数论课小结
2017.8.9数论课小结
一、先是一些整除的性质:
void fenjie(int x) { for (int i = 2; i <= sqrt(x); i++) { while (x % i == 0) { if (!vis[i]) { vis[i] = 1; ans[++tot] = i; } x /= i; } } if (x > 1) ans[++tot] = x; }
分析:这个算法的关键有三个:1.如果除以一个因数就把它除尽. 2.记录这个数有没有进入答案. 3.最后可能还剩下一个数,至于为什么能剩下一个数,我们有一个很重要的定理:每一个数的质因数只有一个大于sqrt(n)的,根据唯一分解定律可以很容易证明.
下面是同余和带余除法的相关知识:
?int gcd(int a, int b) { if (b==0) return a; return gcd(b, a % b); }
int exgcd(int a, int b, int &x, int &y) { if (!b) { x = 1; y = 0; return a; } int r = exgcd(b, a%b, x, y); int t = x; x = y; y = t - a / b*y; return r; }
这只是一组特解,如果我们要求出所有解该怎么办?可以得到一个解系:x = x0 + kb,y = y0 - ka。
不过运用这一个算法有个重要的前提,我们要求解的ax+by=c的c必须等于gcd(a,b),如果不等于该如何处理呢,可以看我的另一篇博客:青蛙的约会:传送门
另外,第一件事要先判断有没有解,利用裴蜀定理即可.
七、逆元
存在的充要条件为(a,b)=1
int inv(int a, int b) { int x, y; exgcd(a, b, x, y); return x; }
八、线性求逆元
for (inv[1] = 1, i = 2; i <= n; ++i) inv[i] = (p - p / i) * inv[p % i] % p;
程序里是p-p/i而不是p/i是为了避免负数.
int sieve(int n, bool isprime[], int prime[]) { int tot = 0; for (int i = 2; i <= n; ++i) isprime[i] = 1; for (int i = 2; i <= n; ++i) if (isprime[i]) { prime[++tot] = i; for (int j = i + i; j <= n; j += i) isprime[j] = 0; } return tot; }
复杂度O(nloglogn)
例7:求[1,10^7]内的所有素数。内存限制1MB
分析:内存只有1mb开不下这么大的数组,但是只要求一个答案,我们可以分段来做,假设我们分为k段,第i段为[l,r],我们枚举不超过sqrt(r)的素数并筛掉[l,r]的数即可,只是枚举素数总要保存的吧,那么我们用一种比较玄学的方法:保存[1,sqrt(10^7)]的素数,这样如果区间被覆盖在左边,直接就可以得到答案,如果被覆盖在右边,我们也稍微枚举一下就好了.
但是这个复杂度还是太高,能不能做到O(n)呢?其实可以,如果我们能够保证每一个数只被删除一次就可以了,怎么保证呢?我们利用数的最小分解来分析:比如一个数18,它的分解是唯一的,但是分解的数的排列并不是唯一的,比如2*3*3,3*2*3,我们只让它被第一个删除,怎么样才能做到呢?我们每次添加一个比当前已经分解的最小的质数更小的质数,比如已有了3*3,我们就只能添加2,如果已有2*3,我们就不能再继续搜了,为什么呢?如果我们添加一个5,5*2*3可以先被2*3*5搜到,这样被重复搜索了.这个线性筛的本质就是我们强行给它定一个搜索的顺序和限制,使得每一个数只能被删除一次.
int sieve(int n, int f[], int prime[]) { int tot = 0; for (int i = 2; i <= n; ++i) { if (!f[i]) prime[++tot] = f[i] = i; for (int j = 1; j <= tot; ++j) { int t = i * prime[j]; if (t > n) break; f[t] = prime[j]; if (f[i] == prime[j]) break; } } }
这个代码中f[i]就是i被分解的最小质因数,如果我们枚举的素数正好等于f[i],也就不能继续了.
暂且先整理这么多吧,内容实在是太多了,以后我会慢慢更新的.
2017.8.9数论课小结