这一章节讲的主要内容是数论方面的知识,设计到的知识点有:
1.整除
2.同余
3.最大公约数
3.1辗转相除法
3.2二进制算法
3.3最小公倍数
3.4扩展欧几里得算法
3.5求解线性同余方程
4.逆元
5.中国剩余定理
6.斐波那契数
7.卡特兰数
8.素数
8.1素数的判定
8.2素数的相关定理
8.3Miller-Rabin素数测试
8.4欧拉定理
8.5Pollard Rho算法求大数因子
9.BSGS及扩展算法
10.欧拉函数的线性筛法
其中Pollard Rho算法没有能够掌握,斐波那契数列通项公式的推导不是很熟,其它的知识点都已了解,历时:7天.
一、整除
关于整除,可以理解为一个数是另一个数的倍数,核心都围绕在这个倍数上,如果已知a|b,那么一般都可以假设b=ak,然后进行推导.如果不能整除,往往会有一个余数r,假设b%a=r,那么b = ak+r,当遇到余数的时候,通过余数展开式进行推导也是一种比较常用的方法.
例1:
2277: [Poi2011]Strongbox
Time Limit: 60 Sec Memory Limit: 32 MBSubmit: 498 Solved: 218
[Submit][Status][Discuss]
Description
有一个密码箱,0到n-1中的某些整数是它的密码。
且满足,如果a和b都是它的密码,那么(a+b)%n也是它的密码(a,b可以相等)
某人试了k次密码,前k-1次都失败了,最后一次成功了。
问:该密码箱最多有多少不同的密码。
Input
第一行n,k
下面一行k个整数,表示每次试的密码
保证存在合法解
1<=k<=250000 k<=n<=10^14
Output
一行,表示结果
Sample Input
28 31 10 38 24
Sample Output
#include <cstdio> #include <cmath> #include <cstring> #include <iostream> #include <algorithm> using namespace std; typedef long long ll; ll n,k,a[250010],ans = n,cnt; bool check(ll x) { for (int i = 1; i <= cnt; i++) if (a[i] % x == 0) return false; return true; } ll gcd(ll a,ll b) { if (!b) return a; return gcd(b,a % b); } int main() { scanf("%lld%lld",&n,&k); for (int i = 1; i <= k; i++) { scanf("%lld",&a[i]); a[i] = gcd(a[i],n); } sort(a + 1,a + k); for (int i = 1; i < k; i++) if (a[i] != a[i - 1]) a[++cnt] = a[i]; for (ll i = 1; i <= sqrt(a[k]); i++) if (a[k] % i == 0) { if (check(i)) { ans = n / i; break; } else if (check(a[k] / i)) ans = n / a[k] * i; } printf("%lld ",ans); return 0; }
总结:拿到一道题,一定要清楚地知道自己是要求解什么.对于这道题,如果没有两个结论,根本不知道自己要编程求啥.还要学会从结论来构造式子,比如我猜测gcd(a,n)是密码,那么我就要构造一个方程使得结果正好是让gcd(a,n)是密码,我能用什么知识解决这道题,我还需要什么信息,我就构造,得到这些信息.最后就是同余式中的变形,模数在同余式中是可以任意加减的,可以通过这个对式子进行适当变形.同样的,如果一个式子出现了多个变量,那么我也可以通过把其中一个变量当作模数来变得只剩下一个变量,一个比较直观的例子是中国剩余定理非互质版的推导.
二、同余
同余一般是在题目给定了模数下才会用到,但是有一些特殊情况比如数太大存不下,只考虑它在同余下的意义也是可以的.比较经典的例子就是:noip2014解方程.同余的运算基本上和四则运算差不多,只不过没有定义除法!涉及到除法就必须要用到逆元!比较常用的性质就是同幂性:a ≡ b (mod p) ---> a^n ≡ b^n (mod p).还有同乘性.
幂数取模问题:若幂次不大,则直接快速幂运算即可.若幂次很大,则用欧拉定理降幂,经典例题1:传送门,经典例题2:传送门
三、最大公约数
例2:欧几里得的游戏
题目描述
欧几里德的两个后代Stan和Ollie正在玩一种数字游戏,这个游戏是他们的祖先欧几里德发明的。给定两个正整数M和N,从Stan开始,从其中较大的一个数,减去较小的数的正整数倍,当然,得到的数不能小于0。然后是Ollie,对刚才得到的数,和M,N中较小的那个数,再进行同样的操作……直到一个人得到了0,他就取得了胜利。下面是他们用(25,7)两个数游戏的过程:
Start:25 7
Stan:11 7
Ollie:4 7
Stan:4 3
Ollie:1 3
Stan:1 0
Stan赢得了游戏的胜利。
现在,假设他们完美地操作,谁会取得胜利呢?
输入输出格式
输入格式:
第一行为测试数据的组数C。下面有C行,每行为一组数据,包含两个正整数M, N。(M, N不超过长整型。)
输出格式:
对每组输入数据输出一行,如果Stan胜利,则输出“Stan wins”;否则输出“Ollie wins”
输入输出样例
Stan wins Ollie wins
分析:可以用sg函数给秒掉,不过分析一下还是能发现规律的.设当前较大的数为m,较小的数为n,如果m/n==1,那么只能进行一种操作,如果m/n>1,那么我可以拿(m/n - 1) * n个,下一次对手就只能拿n个,进入到下一状态,我也可以全部拿完,让对手进入下一状态,也就是说如果我先到m/n>1的状态,那么我就掌控的局势,那么不断地辗转相除,更新答案即可.
#include <cstdio> #include <cmath> #include <cstring> #include <iostream> #include <algorithm> using namespace std; int c,f = 1; long long a,b; int main() { scanf("%d",&c); while (c--) { f = 1; scanf("%lld%lld",&a,&b); if (a < b) swap(a,b); while(b && a / b == 1 && a % b) { f = -f; long long t = a % b; a = b; b = t; } if (f == 1) puts("Stan wins"); else puts("Ollie wins"); } return 0; }
例3:
1477: 青蛙的约会
Time Limit: 2 Sec Memory Limit: 64 MBSubmit: 712 Solved: 416
[Submit][Status][Discuss]
Description
Input
Output
Sample Input
Sample Output
#include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; long long x, y, m, n, L, ansx, ansy; long long gcd(long long a, long long b) { return b == 0 ? a : gcd(b, a%b); } long long exgcd(long long a, long long b, long long &x, long long&y) { if (b == 0) { x = 1; y = 0; return a; } long long r = exgcd(b, a%b, x, y); long long t = x; x = y; y = t - a / b * y; return r; } int main() { scanf("%lld%lld%lld%lld%lld", &x, &y, &m, &n, &L); if (x == y) { printf("0"); return 0; } else { long long a = m - n, b = y - x; long long t = exgcd(a, L, ansx, ansy); if (b % t == 0) { ansx = ansx * b / t; int tt = abs(L / t); ansx = (ansx % tt + tt) % tt; printf("%d", ansx); } else printf("Impossible"); } return 0; }
四、逆元
主要是知道逆元是什么东西,干什么用的,怎么求,如何递推地求出很多逆元.如果x和模数p互素,那么可以直接利用费马小定理来求.只需要求出x^(p-2) mod p就可以了.如果不满足这个要求,就利用扩展欧几里得算法来求.效率上可能扩展欧几里得算法的常数要小,但是费马小定理好写一下.线性求逆元关键就是记住一个式子:q[i] = (p - p/i) * q[p % i] % p.
例4:
Time Limit: 1000MS | Memory Limit: 30000K | |
Total Submissions: 23172 | Accepted: 5769 |
Description
Input
Output
Sample Input
2 3
Sample Output
15
Hint
The natural divisors of 8 are: 1,2,4,8. Their sum is 15.
15 modulo 9901 is 15 (that should be output).
Source
#include <cstdio> #include <cstring> #include <iostream> #include <algorithm> #include <cmath> using namespace std; const int mod = 9901; long long cnt[100010],wh[100010],tot,ans = 1; int a,b; void exgcd(long long a,long long b,long long &x,long long &y) { if (!b) { x = 1; y = 0; return; } exgcd(b,a % b,x,y); long long t = x; x = y; y = t - (a / b) * y; return; } long long qpow(long long a,long long b) { long long ans = 1; while (b) { if (b & 1) ans = (ans * a) % mod; a = (a * a) % mod; b >>= 1; } return ans; } int main() { scanf("%d%d",&a,&b); for (int i = 2; i <= sqrt(a); i++) { if (a % i == 0) { wh[++tot] = i; while (a % i == 0) { a /= i; cnt[i]++; } } } if (a) { wh[++tot] = a; cnt[a]++; } for (int i = 1; i <= tot; i++) { long long x,y; exgcd(wh[i] - 1,mod,x,y); ans = (ans * (( qpow(wh[i],cnt[wh[i]] * b + 1) - 1)* x) % mod) % mod; } printf("%lld ",ans); return 0; }
总结:遇到跟一个数因数有关的题第一想法就是唯一分解定律!
五、斐波那契数列
斐波那契数列有几个非常常用的题型:1.找规律,一般有关斐波那契数列的题的规律都和斐波那契数列有关.2.矩阵快速幂求某一项.3.利用循环节取模算.经典例题1:传送门,经典例题2:传送门,裸的斐波那契数列快速求第n项的话套用公式:,如果n特别特别大的话,可以不管后面那一部分的.经典例题3:传送门
六、卡特兰数:传送门
七、Miller-Rabin素数测试
原理是利用费马小定理,不一定成立,一般选用2,3,5,7这4个数作为模数检验,如果费马小定理一直都成立,那么这个数几乎就是质数.
例5:
2190: [SDOI2008]仪仗队
Time Limit: 10 Sec Memory Limit: 259 MBSubmit: 3353 Solved: 2168
[Submit][Status][Discuss]
Description
作为体育委员,C君负责这次运动会仪仗队的训练。仪仗队是由学生组成的N * N的方阵,为了保证队伍在行进中整齐划一,C君会跟在仪仗队的左后方,根据其视线所及的学生人数来判断队伍是否整齐(如下图)。 现在,C君希望你告诉他队伍整齐时能看到的学生人数。
Input
共一个数N。
Output
共一个数,即C君应看到的学生人数。
Sample Input
Sample Output
HINT
【数据规模和约定】 对于 100% 的数据,1 ≤ N ≤ 40000
#include <cmath> #include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; int n,ans,phi[40010]; void getphi() { phi[1] = 1; for (int i = 2; i <= n; i++) if (!phi[i]) for (int j = i; j <= n; j+=i) { if (!phi[j]) phi[j] = j; phi[j] = phi[j] / i * (i - 1); } } int main() { scanf("%d",&n); getphi(); for (int i = 2; i < n; i++) ans += phi[i]; printf("%d ",ans * 2 + 3); return 0; }
八、BSGS及其扩展算法
这个算法是用来求解a ^ x = b(mod p)中的x的一个根号复杂度的算法.取m=sqrt(p),x = i*m - j.那么式子就变成了:
a^(i*m) = b*a^j (mod p).先枚举j,将右边式子的结果对应的j存到hash表中,再来枚举i,在hash表中查找对应结果的j.注意:如果a%p==0那么是无解的.
例6:
2242: [SDOI2011]计算器
Time Limit: 10 Sec Memory Limit: 512 MBSubmit: 4699 Solved: 1782
[Submit][Status][Discuss]
Description
Input
输入包含多组数据。
Output
Sample Input
3 1
2 1 3
2 2 3
2 3 3
【样例输入2】
3 2
2 1 3
2 2 3
2 3 3
【数据规模和约定】
对于100%的数据,1<=y,z,p<=10^9,为质数,1<=T<=10。
Sample Output
2
1
2
【样例输出2】
2
1
0
HINT
Source
#include <cstdio> #include <cmath> #include <map> #include <cstring> #include <iostream> #include <algorithm> using namespace std; int T, K; typedef long long ll; ll y, z, p, x, block; map <ll, ll> m; ll qpow(ll a, ll b, ll mod) { ll res = 1; while (b) { if (b & 1) res = (res * a) % mod; a = (a * a) % mod; b >>= 1; } return res; } void solve1() { scanf("%lld%lld%lld", &y, &z, &p); printf("%lld ", qpow(y, z, p)); } ll exgcd(ll a, ll b, ll &x, ll &y) { if (!b) { x = 1; y = 0; return a; } ll temp = exgcd(b, a % b, x, y); ll t = x; x = y; y = t - (a / b) * y; return temp; } void solve2() { scanf("%lld%lld%lld", &y, &z, &p); ll tx, ty, td; td = exgcd(y, p, tx, ty); if (z % td != 0) puts("Orz, I cannot find x!"); else { tx = tx * (z / td) % p; ll mmod = p / td; tx = (tx % mmod + mmod) % mmod; printf("%lld ", tx); } } void solve3() { scanf("%lld%lld%lld", &y, &z, &p); m.clear(); if (y % p == 0) { puts("Orz, I cannot find x!"); return; } else { block = ceil(sqrt(p)); ll ans; for (int i = 0; i <= block; i++) { if (i == 0) { ans = z % p; m[ans] = i; continue; } ans = (ans * y) % p; m[ans] = i; } ll t = qpow(y, block, p); ans = 1; for (int i = 1; i <= block; i++) { ans = (ans * t) % p; if (m[ans]) { ll t = i * block - m[ans]; printf("%lld ", (t % p + p) % p); return; } } } puts("Orz, I cannot find x!"); } int main() { scanf("%d%d", &T, &K); while (T--) { if (K == 1) solve1(); if (K == 2) solve2(); if (K == 3) solve3(); } return 0; }
当p不是质数的时候,就要用到扩展BSGS算法了.思想就是不断地提gcd出来,直到互质,然后套用普通的BSGS算法.
例7:
1467: Pku3243 clever Y
Time Limit: 4 Sec Memory Limit: 64 MBSubmit: 313 Solved: 181
[Submit][Status][Discuss]
Description
Input
Output
Sample Input
2 4 3
0 0 0
Sample Output
No Solution
HINT
Source
分析:扩展BSGS.对于P与A不互质的情况,我们就不断地提gcd出来,直到互质.然后换元套用普通的bsgs算法即可.具体的解法:--来自Clove_unique的博客.事实上只需要根据式子就可以用普通的BSGS算法了,求出x-k后,加上k就是x.
一个思想:从普通算法向扩展算法的延伸,如果由互质版本变成不互质版本,想办法变成互质版本,可以取gcd.一定要是等价变形.在变形的时候要判断无解的情况.
#include <cstdio> #include <cmath> #include <map> #include <cstring> #include <iostream> #include <algorithm> using namespace std; typedef long long ll; ll a, b, p, block; map <ll, ll> m; ll gcd(ll a, ll b) { if (!b) return a; return gcd(b, a % b); } ll qpow(ll a, ll b) { ll res = 1; while (b) { if (b & 1) res = (res * a) % p; a = (a * a) % p; b >>= 1; } return res; } ll exbsgs(ll a, ll b, ll p) { if (b == 1) return 0; ll temp = gcd(a, p), cnt = 0, t = 1; while (temp != 1) { if (b % temp != 0) return -1; cnt++; b /= temp; p /= temp; t = t * (a / temp) % p; temp = gcd(a, p); } m.clear(); block = sqrt(p); ll res = b, ta = qpow(a, block); for (ll i = 1; i <= block; i++) { res = res * a % p; m[res] = i; } for (ll i = 1; i <= block; i++) { t = t * ta % p; if (m[t]) return i * block - m[t] + cnt; } return -1; } int main() { while (scanf("%lld%lld%lld", &a, &p, &b) && a && b && p) { ll ans = exbsgs(a % p, b % p, p); if (ans != -1) printf("%lld ", ans); else puts("No Solution"); } return 0; }
中国剩余定理和BSGS的互质版本与非互质版本的转换都是依靠不断提取gcd,来变成互质的版本.
九、欧拉函数的线性筛法
因为欧拉函数是积性函数,所以可以在线性筛素数的同时把欧拉函数给求出来:
void shai() { 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 = i * prime[j]; if (t > maxn) break; vis[t] = 1; if (i % prime[j] == 0) { phi[t] = pht[i] * prime[j]; break; } phi[t] = phi[i] * (prime[j] - 1); } } }
习题2:传送门
习题3:传送门
习题4:传送门
其它资料:传送门