2017.8.9数论课小结
Posted zbtrs
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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],也就不能继续了.
十、线性同余方程
int CRT(const int a[], const int m[], int n) { int M = 1, ret = 0; for (int i = 1; i <= n; ++i) M *= m[i]; for (int i = 1; i <= n; ++i) { int Mi = M / m[i], ti = inv(Mi, m[i]); ret = (ret + a[i] * Mi * ti) % M; } return ret; }
例8:今有物不知其数,三三数之剩二,五五数之剩三,七七数之剩二,问物几何?
分析:
第二个证明的n=p1^k1*p2^k2*...*pn^kn,不等于第一个证明中的n,当初为此迷惑了半天......
由此可以看出积性函数可以先拆成乘积的形式然后再合并就能得到公式.
欧拉函数的性质:1.phi(p^k)=(p-1)*p^(k-1) 2.积性函数(当a,b互质时!) 3.当b是质数,a%b==0,phi(a*b)=phi(a)*b,显然也可以利用积性函数来得到.
代码:
int phi(int x) { int ret = x; for (int i = 2; i * i <= x; ++i) if (x % i == 0) { while (x % i == 0) x /= i; ret = ret / i * (i - 1); } if (x > 1) ret = ret / x * (x - 1); return ret; }
这份代码先除后乘的原因是防止溢出,我们在比赛的时候,如果数据范围大也要这么处理!
其实欧拉函数也可以像筛素数那样打一个表出来,具体的实现:
for(i=1; i<=maxn; i++) p[i]=i; for(i=2; i<=maxn; i+=2) p[i]/=2; for(i=3; i<=maxn; i+=2) if(p[i]==i) { for(j=i; j<=maxn; j+=i) p[j]=p[j]/i*(i-1); }
例10:
2705: [SDOI2012]Longge的问题
Submit: 3160 Solved: 1973
[Submit][Status][Discuss]
Description
Input
Output
Sample Input
Sample Output
HINT
【数据范围】
对于60%的数据,0<N<=2^16。
对于100%的数据,0<N<=2^32。
Source
分析:如果直接暴力求gcd,n^2的枚举时间加上每次求gcd的时间,直接爆掉,那么能不能用更好的方法做呢?
我们可以换个思路,要求Σgcd(i,n),我们假设gcd(i,n) = g,也就是我们要求以g为最大公约数的(i,n)有多少对,然后g对答案的贡献就是g*个数,显然,这个g是n的约数,对于约数的枚举我们有一个技巧,就是只枚举到它的sqrt即可,和它成对的一个约数就是n/i,前提是i != sqrt(n).如果g = 1,我们可以直接用欧拉函数来统计,如果g != 1呢?那么我们可以把i,n写作i = i\'*g,n = n\'*g,i和n同时除以g,那么n和i就互质了,这样直接用欧拉函数就可以解决问题了.
#include <cstdio> #include <queue> #include <cstring> #include <iostream> #include <algorithm> #include <cmath> using namespace std; long long n,ans; long long phi(long long x) { long long res = x; for (long long i = 2; i <= sqrt(x); i++) { if (x % i == 0) { while (x % i == 0) x /= i; res = res / i * (i - 1); } } if (x > 1) res = res / x * (x - 1); return res; } int main() { scanf("%lld", &n); for (long long i = 1; i <= sqrt(n); i++) { if (n % i == 0) { ans += i * phi(n / i); if (i * i < n) ans += n / i * phi(i); } } printf("%lld\\n", ans); return 0; }
例11:
2186: [Sdoi2008]沙拉公主的困惑
Time Limit: 10 Sec Memory Limit: 259 MBSubmit: 4549 Solved: 1567
[Submit][Status][Discuss]
Description
大富翁国因为通货膨胀,以及假钞泛滥,政府决定推出一项新的政策:现有钞票编号范围为1到N的阶乘,但是,政府只发行编号与M!互质的钞票。房地产第一大户沙拉公主决定预测一下大富翁国现在所有真钞票的数量。现在,请你帮助沙拉公主解决这个问题,由于可能张数非常大,你只需计算出对R取模后的答案即可。R是一个质数。
Input
第一行为两个整数T,R。R<=10^9+10,T<=10000,表示该组中测试数据数目,R为模后面T行,每行一对整数N,M,见题目描述 m<=n
Output
共T行,对于每一对N,M,输出1至N!中与M!素质的数的数量对R取模后的值
Sample Input
4 2
Sample Output
数据范围:
对于100%的数据,1 < = N , M < = 10000000
#include <cstdio> #include <queue> #include <cstring> #include <iostream> #include <algorithm> #include <cmath> using namespace std; const int maxn = 10000001; int t, p,n,m; long long f[maxn], ans[maxn],prime[664580],niyuan[maxn]; bool vis[maxn]; void init() { long long tot = 0; for (int i = 2; i <= maxn; i++) { if (!vis[i]) prime[++tot] = i; for (int j = 1; j <= tot; j++) { if (prime[j] * i > maxn) break; vis[prime[j] * i] = 1; if (i % prime[j] == 0) break; } } f[1] = 1; for (int i = 2; i <= maxn; i++) f[i] = f[i - 1] * i % p; niyuan[1] = 1; for (int i = 2; i <= maxn && i < p; i++) niyuan[i] = (p - p / i) * niyuan[p % i] % p; ans[1] = 1; for (int i = 2; i <= maxn; i++) { if (!vis[i]) ans[i] = ans[i - 1] * (i - 1) % p * niyuan[i % p] % p; else ans[i] = ans[i - 1]; } } int main() { scanf("%d%d", &t, &p); init(); for (int i = 1; i <= t; i++) { scanf("%d%d", &n, &m); printf("%d\\n", f[n] * ans[m] % p); } return 0; }
十三、欧拉定理
不过求逆元一般是用扩展欧几里得来做,常数小,效率高,这两种方法的限制较多.
3884: 上帝与集合的正确用法
Time Limit: 5 Sec Memory Limit: 128 MBSubmit: 2462 Solved: 1093
[Submit][Status][Discuss]
Description
Input
Output
Sample Input
2
3
6
Sample Output
1
4
HINT
#include <cstdio> #include <queue> #include <cstring> #include <iostream> #include <algorithm> #include <cmath> using namespace std; int t, p; long long qpow(long long a, long long b, long long mod) { long long res = 1; while (b) { if (b & 1) res = res * a % mod; b >>= 1; a = a * a % mod; } return res; } long long phi(int x) { long long res = x; for (long long i = 2; i <= sqrt(x);i++) { if (x % i == 0) { while (x % i == 0) x /= i; res = res / i * (i - 1); } } if (x > 1) res = res / x * (x - 1); return res; } long long solve(int x) { if (x == 1) return 0; long long t = phi(x); return qpow(2, solve(t) + t, x); } int main() { scanf("%d", &t); while (t--) { scanf("%d", &p); printf("%lld\\n",solve(p)); } return 0; }
十四、积性函数
线性筛与积性函数之间有一定的联系,如果题目要求我们求出质数的同时求出欧拉函数值,我们可以利用积性函数的性质在筛法中求出欧拉函数值:
void shai() { phi[1] = 1; for (int i = 2; i <= maxn; i++) { if (!vis[i]) { prime[++tot] = i; phi[i] = i - 1; } for (int j = 1; j <= tot; j++) { int t = prime[j] * i; if (t > maxn) break; vis[t] = 1; if (i % prime[j] == 0) { phi[t] = phi[i] * j; break; } else phi[t] = phi[i] * phi[j]; } } }
现在来解释一下代码:这个线性筛和之前的模板不太一样,原理是相同的,当i % prime[j] == 0的时候就代表找到了最小表示的最小质数,不能继续往下筛了,如果i是一个质数,那么它的phi肯定是i-1,如果i,prime[j]互质,那么利用积性函数
以上是关于2017.8.9数论课小结的主要内容,如果未能解决你的问题,请参考以下文章