质数的判定
试除法判定质数
==质数==:对于>1的整数,如果只包含1和本身这两个约束,就被称为质数或者素数
O(sqrt(n))
1 2 3 4 5 6 7 8 9
| bool prime(int x){ if(x<2) return false; else{ for(int i=2;i<=x/i;i++){ if(x%i==0) return false; } } return true; }
|
分解质因数
从小到大枚举所有数
O(sqrt(n))
1 2 3 4 5 6 7 8 9 10 11 12
| void quite(int x){ int n=x; for(int i=2;i<=n/i;i++){ int s=0; while(x%i==0){ s++; x=x/i; } if(s!=0) cout<<i<<" "<<s<<endl; } if(x>1) cout<<x<<" 1"<<endl; }
|
筛质数
从前往后,把每一个数的倍数筛掉
1 2 3 4 5 6 7 8 9 10 11 12 13
| void prime(){ if(n<2) cnt=0; else { for(int i=2;i<=n;i++){ if(!st[i]){ cnt++; for(int j=i;j<=n;j+=i){ if(!st[j]) st[j]=true; } } } } }
|
<埃氏筛法>优化:只有是质数,才需要筛掉倍数
质数定理:1~n中有(n/lnn)个质数
时间复杂度:(n*lnn/lnn)=n
核心:只会被最小的质因子筛掉
1 2 3 4 5 6 7 8 9 10 11 12 13
| void prime(){ if(n<2) cnt=0; else { 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; } } } }
|
1 2 3
| 1.if(i%primes[j]==0),p[j]一定是i的最小质因子,p[j]一定是p[j]*i的最小质因子 2.if(i%primes[j]!=0),则p[j]一定小于i的最小质因子,p[j]一定是p[j]*i的最小质因子 3.对于任何一个合数,假设pj是x的最小质因子,当i枚举到x/pj的时候一定会被筛掉
|
约数
试除法求一个数的所有约数
1 2 3 4 5 6 7 8 9 10 11 12
| void yy(int x){ vector<int> res; for(int i=1;i<=x/i;i++){ if(x%i==0){ res.push_back(i); if(i!=x/i) res.push_back(x/i); } } sort(res.begin(),res.end()); for(auto t:res) cout<<t<<" "; cout<<endl; }
|
约数个数
int范围内,约数个数最多的是1500个左右
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| #include<iostream> #include<algorithm> #include<unordered_map> using namespace std; const int mod=1e9+7; typedef long long LL; int main(){ int n; cin>>n; unordered_map<int,int> primes; while(n--){ int x; cin>>x; for(int i=2;i<=x/i;i++){ while(x%i==0){ primes[i]++; x=x/i; } } if(x>1) { primes[x]++; } } LL res=1; for(auto prime:primes) res=res*(1+prime.second)%mod; cout<<res; return 0; }
|
约数之和
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| #include<iostream> #include<algorithm> #include<unordered_map> using namespace std; const int mod=1e9+7; typedef long long LL; int main(){ int n; cin>>n; unordered_map<int,int> primes; while(n--){ int x; cin>>x; for(int i=2;i<=x/i;i++){ while(x%i==0){ primes[i]++; x=x/i; } } if(x>1) { primes[x]++; } } LL res=1; LL t=1; for(auto prime:primes) { t=1; int p=prime.first; int a=prime.second; while(a--){ t=(t*p+1)%mod; } res=res*t%mod; } cout<<res; return 0; }
|
最大公约数(欧几里得算法/辗转相除法)
if(d|a&&d|b)
->d|(a+b)
(a,b)
=(b,a mod b)
1 2 3
| 证明:若d|a,且d|(a-c*b) 则d|(a-c*b+c*b) 则d|a
|
因此,左边的最大公约数=右边的最大公约数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| #include<iostream> #include<algorithm> using namespace std; int gcb(int a,int b){ return b ? gcb(b,a%b):a; } int main(){ int n; cin>>n; while(n--){ int a,b; cin>>a>>b; cout<<gcb(a,b)<<endl; } return 0; }
|
欧拉函数
欧拉函数的定义:
1~N中和N互质的个数
- 从1~N中去掉p1,p2,…,pk的倍数
- 加上所有pi*pj的倍数
- 减去所有pi*pj*pk的倍数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| #include<iostream> #include<algorithm> using namespace std; int main(){ int n; cin>>n; while(n--){ int x; cin>>x; int res=x; for(int i=2;i<=x/i;i++){ if(x%i==0){ res=res/i*(i-1); x=x/i; while(x%i==0){ x=x/i; } } } if(x>1) res=res/x*(x-1); cout<<res<<endl; } return 0; }
|
筛法求欧拉函数
求1~n中每个数的欧拉函数
O(n)时间复杂度下求得
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| long long pri(int x){ phi[1]=1; int cnt=0; for(int i=2;i<=x;i++) { if(!st[i]) { primes[cnt++]=i; phi[i]=i-1; } for(int j=0;primes[j]<=x/i;j++){ st[i*primes[j]]=true; if(i%primes[j]==0) { phi[primes[j]*i]=primes[j]*phi[i]; break; } phi[primes[j]*i]=(primes[j]-1)*phi[i]; } } long long res=0; for(int i=1;i<=x;i++) res+=phi[i]; return res; }
|
快速幂
预处理:a^1^mod
p ,a^2^mod
p ,a^4^mod
p ,a^8^mod
p ……a^(2^log2k^)mod
p
每一个数都是上一个数的平方mod
p
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| #include<iostream> #include<algorithm> using namespace std; typedef long long LL; int pi(int a,int k,int p){ int res=1; while(k){ if(k&1==1) { res=(LL)res*a%p; } k=k>>1; a=(LL)a*a%p; } return res; } int main(){ int a,k,p; int n; cin>>n; while(n--){ scanf("%d%d%d",&a,&k,&p); printf("%d\n",pi(a,k,p)); } return 0; }
|
快速幂求逆元
==乘法逆元==:
找到x,使得b*x%m=1
比如,3*x%5==1,2是3的模5逆元,3^5-2^%5=2,可以求出来
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| #include<iostream> #include<algorithm> using namespace std; typedef long long LL; int pi(int a,int k,int p){ int res=1; while(k){ if(k&1==1) { res=(LL)res*a%p; } k=k>>1; a=(LL)a*a%p; } return res; } int main(){ int a,k,p; int n; cin>>n; while(n--){ scanf("%d%d",&a,&p); if(a%p){ printf("%d\n",pi(a,p-2,p)); } else puts("impossible"); } return 0; }
|
扩展欧几里得算法
裴蜀定理:
对于任意一对正整数a,b,那么一定存在整数x,y,使得ax+by=(a,b)–a,b的最大公约数(x,y是最小的正整数)
(a,b)=(b,a%b)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| #include<iostream> #include<algorithm> using namespace std; int exgcd(int a,int b,int &x,int &y){ if(!b){ x=1,y=0; return a; } else{ int d=exgcd(b,a%b,y,x); y-=(a/b)*x; return d; } } int main() { int n; scanf("%d",&n); while(n--){ int a,b,x,y; scanf("%d%d",&a,&b); exgcd(a,b,x,y); printf("%d %d\n",x,y); } return 0; }
|
中国剩余定理
m1,m2,……两两互质
给定一堆互质的数,使得
x%m1=a1
x%m2=a2
x%m3=a3
x%m4=a4
M=m1*m2*…*mk
Mi=M/mi(即除了mi之外所有的乘积)
Mi^-1^表示Mi模mi的逆,用扩展欧几里得算法求,等价于ax%m=1
x=a1M1M1^-1^+a2M2M2^-1^+a3M3M3^-1^+…+akMkMk^-1^
高斯消元
在n^3^时间复杂度内,求解n个未知数的n个一元线性方程组:
a11x1+a12x2+a13x3+…+a1nxn+=b1
a21x1+a22x2+a23x3+…+a2nxn+=b2
…
an1x1+an2x2+an3x3+…+annxn+=bn
==解==:
无解:0=b
无穷多组解:方程数<未知数的个数
唯一解:完美阶梯型
==做法==(初等行列变换)枚举每一列:
找到绝对值最大的一行
将该行换到最上面
把该行的第一个数变成1
把下面所有行的第c列消成0
可以变成上三角的形式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
| #include<iostream> #include<algorithm> #include<cstdio> #include<cmath> using namespace std; const int N=110; const double egg=1e-6; double a[N][N]; int r=0,n; void out() { for(int i=0;i<n;i++){ for(int j=0;j<=n;j++){ printf("%.2lf ",a[i][j]); } puts(""); } puts(""); } int gause(){ for(int c=0;c<n;c++){ int t=r; for(int i=t;i<n;i++){ if(fabs(a[t][c])<fabs(a[i][c])) t=i; } if(fabs(a[t][c])<egg) continue; for(int i=c;i<=n;i++){ swap(a[r][i],a[t][i]); } for(int i=n;i>=c;i--){ a[r][i]/=a[r][c]; } for(int i=r+1;i<n;i++){ for(int j=n;j>=c;j--){ a[i][j]-=a[r][j]*a[i][c]; } } r++; } if(r<n){ for(int i=r;i<n;i++){ if(fabs(a[i][n])>egg) return -1; } return 1; } if(n>1){ for(int i=n-2;i>=0;i--){ for(int j=i+1;j<n;j++){ a[i][n]-=a[i][j]*a[j][n]; } } } return 0; } int main(){ cin>>n; for(int i=0;i<n;i++){ for(int j=0;j<=n;j++){ scanf("%lf",&a[i][j]); } } int t=gause(); if(t==-1) puts("No solution"); else if(t==1) puts("Infinite group solutions"); else{ for(int i=0;i<n;i++) printf("%.2lf\n",a[i][n]); }
|
求组合数
组数 |
a,b范围 |
处理方式 |
10万组 |
(1,2000) |
递推 |
1万组 |
(1,100000) |
预处理阶乘 |
20组 |
(1,200000) |
卢卡斯定理 |
|
|
|
Ca^b^=a*(a-1)*…*(a-b+1)/(1*2*…*b)=(a!)/(b!)/(a-b!)
Ca^b^=Ca-1^b^+Ca-1^b-1^//从a个苹果选b个,不包含某一个苹果,包含某一个苹果
递推
0<a,b<2000,
100000组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| #include<iostream> using namespace std; const int mod=1e9+7; const int N=2010; long long f[N][N]; int main(){ int n; cin>>n; for(int i=0;i<N;i++){ for(int j=0;j<=i;j++){ if(!j) { f[i][j]=1; } else f[i][j]=(f[i-1][j]+f[i-1][j-1])%mod; } } while(n--){ int a,b; scanf("%d%d",&a,&b); printf("%lld\n",f[a][b]); } }
|
预处理
1≤a,b≤100000
10000组
预处理阶乘
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| #include<iostream> #include<algorithm> using namespace std; typedef long long ll; const int N=1e5+10,mod=1e9+7; ll fact[N],infact[N]; ll qmi(int a,int k,int p){ ll res=1; while(k){ if(k&1) res=(ll)res*a%p; a=(ll)a*a%p; k>>=1; } return res; } int main() { int n; cin>>n; fact[0]=infact[0]=1; for(int i=1;i<N;i++){ fact[i]=(ll)i*fact[i-1]%mod; infact[i]=(ll)infact[i-1]*qmi(i,mod-2,mod)%mod; } while(n--){ int a,b; cin>>a>>b; cout<<(ll)fact[a]*infact[a-b]%mod*infact[b]%mod<<endl; } return 0; }
|
卢卡斯定理
20组
1≤a,b≤1e18
Ca^b^ % p=Camodp^bmodp^*Ca/p^b/p^
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| #include<iostream> #include<algorithm> using namespace std; typedef long long ll; ll pmi(int a,int k,int p){ int res=1; while(k){ if(k&1) res=(ll)res*a%p; a=(ll)a*a%p; k=k>>1; } return res; } ll C(ll a,ll b,ll p) { if(b>a) { return 0; } int res=1; if(b>=1){ for(int i=a,j=b;j>=1;j--,i--){ res=(ll)res*i%p; res=(ll)res*pmi(j,p-2,p)%p; } return res; } return 1; } int ludas(ll a,ll b,ll p){ if(a<p&&b<p){ return (ll)C(a,b,p)%p; } else return (ll)C(a%p,b%p,p)*ludas(a/p,b/p,p)%p; } int main(){ int n; cin>>n; while(n--){ ll a,b,p; cin>>a>>b>>p; cout<<ludas(a,b,p)<<endl; } return 0; }
|
高精度
- 找出质数
- 分解质因数
- a!=p的倍数个数+p^2^的倍数个数+p^3^的倍数个数+…+p^k^的倍数个数
- 高精度乘法把因子乘到一起
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| #include<iostream> #include<vector> #include<algorithm> using namespace std; const int N=5010; bool st[N]; int pri[N],cnt,sum[N]; void prime(int x){ for(int i=2;i<=x;i++){ if(!st[i]) pri[cnt++]=i; for(int j=0;pri[j]<=x/i;j++){ st[i*pri[j]]=true; if(i%pri[j]==0) break; } } } int get(int x,int p){ int res=0; while(x!=0){ res=res+x/p; x=x/p; } return res; } vector<int> mul(vector<int> a,int b){ int t=0; vector<int> c; for(int i=0;i<a.size()||t;i++){ if(i<a.size()) t=t+a[i]*b; c.push_back(t%10); t=t/10; } while(c.size()>1&&c.back()==0) c.pop_back(); return c; } int main(){ int a,b; cin>>a>>b; prime(a); for(int i=0;i<cnt;i++){ int p=pri[i]; sum[i]=get(a,p)-get(b,p)-get(a-b,p); } vector<int> res; res.push_back(1); for(int i=0;i<cnt;i++){ for(int j=0;j<sum[i];j++){ res=mul(res,pri[i]); } } for(int i=res.size()-1;i>=0;i--){ printf("%d",res[i]); } return 0; }
|
满足条件的01序列(卡特兰数)
给定 n个0和n个1,它们将按照某种顺序排成长度为2n的序列,求它们能排列成的所有序列中,能够满足任意前缀序列中0的个数都不少于1的个数的序列有多少个。
转化成从原点走路径的问题
从(0,0)走到(6,6)的问题,0是向上走一格,1是向上走一格
每个序列转化成从前往后走的路径,满足任意时刻,上走个数>右走个数,即x≥y
所有的路径的点必须在绿线上方,不经过红色边
所有走法:C12^6^
所有经过红色线的,在第一次经过时,按照红色做轴对称,终点是(5,7),走法数为C12^5^
答案:C12^6^-C12^5^
C2n^n^-C2n^n-1^=C2n^n^/(n+1)->卡特兰数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| #include<iostream> #include<algorithm> using namespace std; typedef long long ll; int qmi(int a,int k,int p){ int res=1; while(k){ if(k&1) res=(ll)res*a%p; a=(ll)a*a%p; k=k>>1; } return res; } const int mod=1e9+7; int main(){ int n; cin>>n; int res=1; for(int i=n+1;i<=2*n;i++){ res=(ll)res*i%mod; } for(int i=1;i<=n+1;i++){ res=(ll)res*qmi(i,mod-2,mod)%mod; } cout<<res; return 0; }
|
容斥原理
S=S1+S2+S3-S4-S5-S6+S7
Cn^1^+Cn^2^+…+Cn^n^=2^n^-1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| #include<iostream> #include<algorithm> using namespace std; typedef long long ll; const int N=20; int p[N]; int main(){ int n,m; cin>>n>>m; for(int i=0;i<m;i++) cin>>p[i]; int res=0; for(int i=1;i<1<<m;i++){ int s=0; int t=1; for(int j=0;j<m;j++){ if(i>>j&1){ if((ll)p[j]*t>n){ t=-1; break; } t=t*p[j]; s++; } } if(t==-1) continue; if(s&1) res=res+n/t; else res=res-n/t; } cout<<res; return 0; }
|
Nim游戏(博弈论)
Nim游戏
给定n堆石子,两位玩家轮流操作,每次操作可以从任意一堆石子中拿走任意数量的石子(可以拿完,但不能不拿),最后无法进行操作的人视为失败。
问如果两人都采用最优策略,先手是否必胜。
必败态:(先手)走不到任何一个必败状态
必胜态:让剩下的状态变成必败态
0^0^0…^0=0
若a1^a2^..ai…^an!=x !=0
x的二进制表示中,最高位为第k位,必然存在至少一个数ai,第k位二进制数位1(如果都是0,不可能出来x)
ai^x<ai
从ai拿走ai-ai^x个石子,剩下个ai^x个
a1^a2^..ai^x…^an!=x^x=0
若a1^a2^..ai…^an!=x =0
不管怎么拿,结局的异或值!=0
要是为0,那么ai^ai‘=0,ai=ai,说明没拿走,不符合规则
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| #include<iostream> #include<algorithm> using namespace std; int main(){ int n; scanf("%d",&n); int res=0; int x; while(n--){ scanf("%d",&x); res=res^x; } if(res!=0) puts("Yes"); else puts("No"); return 0; }
|
台阶-Nim
现在,有一个n级台阶的楼梯,每级台阶上都有若干个石子,其中第i级台阶上有a^i^个石子(i≥1)。两位玩家轮流操作,每次操作可以从任意一级台阶上拿若干个石子放到下一级台阶中(不能不拿)。已经拿到地面上的石子不能再拿,最后无法进行操作的人视为失败。
问如果两人都采用最优策略,先手是否必胜。
奇数级台阶,若a1^a3^..ai…^an!=x !=0(偶数级台阶只需要重复对方步骤即可,奇数级台阶不变)
奇数级台阶,若a1^a3^..ai…^an!=x=0,永远无法翻身
集合-Nim(SG函数)
mex运算
:找到集合当中最小的不存在的自然数
SG(终点)=0
SG(x)=mex(SG(y1),SG(y2)…SG(yk))
重点状态(必败态):SG(x)=0
若SG(x)!=0,证明可以到0,则说明是必胜态
很多图时,SG(x1)^SG(x2)…^SG(xn)=0 必败;SG(x1)^SG(x2)…^SG(xn) !=0必胜
证明: 所有SG(xi)都为0,则SG(x1)^SG(x2)…^SG(xn)=0,必败
SG(x1)^SG(x2)…^SG(xn )=x !=0一定可以找到SG(xi)^x < SG(xi),证明可以把这个局面变成0
SG(x1)^SG(x2)…^SG(xn )=x=0,不管怎么走,亦或出来不是0
现在有两位玩家轮流操作,每次操作可以从任意一堆石子中拿取石子,每次拿取的石子数量必须包含于集合S,最后无法进行操作的人视为失败。
问如果两人都采用最优策略,先手是否必胜。
SG(10)求法如图所示,也可以求出SG(7) SG(5)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| #include<iostream> #include<algorithm> #include<set> #include<cstring> const int N=110,M=10010; int s[N],m,n,f[M]; using namespace std; int sg(int x){ if(f[x]!=-1) return f[x]; set<int> S; for(int i=0;i<m;i++){ int sum=s[i]; if(sum<=x) S.insert(sg(x-s[i])); } for(int i=0;;i++){ if(!S.count(i)) return f[x]=i; } } int main(){ cin>>m; memset(f,-1,sizeof f); for(int i=0;i<m;i++){ cin>>s[i]; } cin>>n; int res=0; for(int i=0;i<n;i++){ int x; cin>>x; res=res^sg(x); } if(res==0) puts("No"); else puts("Yes"); return 0; }
|
拆分Nim
给定n堆石子,两位玩家轮流操作,每次操作可以取走其中的一堆石子,然后放入两堆规模更小的石子(新堆规模可以为0,且两个新堆的石子总数可以大于取走的那堆石子数),最后无法进行操作的人视为失败。
问如果两人都采用最优策略,先手是否必胜。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| #include<iostream> #include<algorithm> #include<cstring> #include<unordered_set> using namespace std; const int N=110; unordered_set<int> S; int f[N]; int sg(int x){ if(f[x]!=-1) return f[x]; for(int i=0;i<x;i++){ for(int j=0;j<=i;j++){ S.insert(sg(i) ^ sg(j)); } } for(int i=0;;i++){ if(!S.count(i)) return f[x]=i; } } int main(){ int n; cin>>n; memset(f,-1,sizeof f); int res=0; for(int i=0;i<n;i++) { int x; cin>>x; res=res^sg(x); } if(res) puts("Yes"); else puts("No"); return 0; }
|