本文最后更新于 2024年10月18日 早上
欧几里得算法
就是求最大公约数的辗转相除法。
数学公式
gcd(a,b)={gcd(b,amodb)a,b=0,b=0
模板
1 2 3 4 5
| int gcd(int a,int b) { if(b==0) return a; return gcd(b,a%b); }
|
拓展欧几里得(exgcd)
适用问题
给定正整数 a,b,求出 ax+by=gcd(a,b) 的一组正整数解。
算法思路
由欧几里得算法得出 ax+by=gcd(a,b)=gcd(b,amodb),代回原式,即为:
bx′+amodb×y′=gcd(b,amodb)
假设我们已知上式的一组解 x′,y′,可以得到:
ax+by=gcd(a,b)=bx′+amodb×y′=bx′+(a−⌊ba⌋×b)y′=ay′+b(x′−⌊ba⌋×y′)
所以
ax+byxy=ay′+b(x′−⌊ba⌋×y′)=y′=x′−ba×y′
我们发现,当 b=0 时,a×1+b×0=a,有一组解
{x=1y=0
模板
直接递归求解即可
1 2 3 4 5 6 7 8 9 10 11 12
| int exgcd(int a,int b,int &x,int &y) { if(b==0) { x=1; y=0; return a; } int g=exgcd(b,a%b,y,x); y=y-a/b*x; return g; }
|
这时,我们已经得到了一组解,下一步是求解 ax+by=c。
求解 ax+by=c
接下来我们需要分类讨论。
设 g=gcd(a,b)。
当 cmodb=0 时,无解。
否则,令 k=gc,
则
ax′k+by′k=g×k=c
得
{x=x′ky=y′k
设 x 的周期 T=gb,则最小正整数解为 (xmodT+T)modT。
乘法逆元
定义
已知 a,p,求 ax≡1(modp) 中的 x,称 x 为 a 关于 p 的逆元。
求解
直接求解 ax+py=1 即可。
最终答案为 (xmodp+p)modp。
费马小定理
当 p 为素数时,a 关于 p 的逆元是 ap−2。
数论分块
balabala
amodb=a−b×⌊ba⌋
一些题
P1516 青蛙的约会
Problem Link
这是一道比较裸的拓欧题。
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
| #define int ll int x,y,m,n,l; int p,q; int exgcd(int a,int b,int &x,int &y) { if(b==0) { x=1; y=0; return a; } int g=exgcd(b,a%b,y,x); y=y-a/b*x; return g; } signed main() { x=read();y=read();m=read();n=read();l=read(); int a=n-m,b=x-y; if(a<0) { a=-a; b=-b; } int ans=exgcd(a,l); if(b%ans!=0) { cout<<"Impossible"<<endl; } else { cout<<((p*(b/ans))%(l/ans)+l/ans)%(l/ans)<<endl; } return 0; }
|
CF1114C Trailing Loves
首先从 10 进制开始分析。求 p 末尾 0 的数量即为求 p 的因数中含有 2 和 5 的数量的最小值。
例如设 x1=23×52,则 x1 的末尾 0 的数量即为 min(3,2)=2。
接下来考虑 (n!)b 的末尾 0 的数量。我们先枚举 b 的因数 b=p1a1×p2a2×…×pkak,若 pim 是 n! 的一个因数,则其对答案的贡献为 ⌊aim⌋。
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
| const int N=2e6+10; int n,b; int pri[N],cnt=0; int t[N]; int ans=INF; signed main() { n=read();b=read(); for(int i=2;i*i<=b;i++) { if(b%i==0) { pri[++cnt]=i; while(b%i==0) b/=i,t[cnt]++; } } if(b>1) pri[++cnt]=b,t[cnt]++; for(int i=1;i<=cnt;i++) { int nt=n; int sum=0; while(nt) { sum+=nt/pri[i]; nt/=pri[i]; } ans=min(sum/t[i],ans); } printf("%lld\n",ans); return 0; }
|
CF1493D GCD of an Array
由于一个集合的最大公因数的一个质因数的指数是这个集合中该质数指数的最小值,可以想到每次动态维护集合 gcd 时,考虑将 y 分解质因数 y=p1e1×p2e2×…×pkek,如果集合中除 ax 以外的其他元素均有可以对答案产生贡献的因数 pi,那么答案就可以加入 pi 产生的贡献。
形式化的,如果我们定义 vp(ai) 为 ai 中质因数 p 的指数,那么若满足
min(vp(a1),…,vp(ax−1),vp(y),vp(ax+1),…,vp(an))>min(vp(a1),vp(a2),…,vp(an))
设 A 为上述两式的差值,则对答案的贡献为 pA。
接下来考虑如何求。可以用线性筛预处理质数,每次操作用预处理的最小质因子分解。
我们用 multiset
记录质数 p 出现在的元素下标,t 数组记录 p 在多少个不同的元素中出现。当执行一次操作时,判断是否能够给答案产生贡献。如果能,那么插入当前的下标,随后暴力将 multiset
中的 1∼n 下标删除一次。
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
| const int N=2e5+10; int n,m; int a[N]; multiset<int> st[N]; int b[N]; int prime[N]; int cnt=0; int t[N]; int ans=1; void pri(int n) { for(int i=2;i<=n;i++) { if(!b[i]) b[i]=i,prime[++cnt]=i; for(int j=1;j<=cnt;j++) { if(prime[j]>b[i] || prime[j]>n/i) break; b[i*prime[j]]=prime[j]; } } } void modify(int p,int x) { while(x!=1) { int minn=b[x]; if(st[minn].find(p)==st[minn].end()) t[minn]++; st[minn].insert(p); if(t[minn]==n) { for(int i=1;i<=n;i++) { st[minn].erase(st[minn].find(i)); if(st[minn].find(i)==st[minn].end()) t[minn]--; } ans=(ans*minn)%mod; } x/=minn; } } signed main() { pri(N-1); n=read();m=read(); for(int i=1;i<=n;i++) { a[i]=read(); } for(int i=1;i<=n;i++) { modify(i,a[i]); } while(m--) { int p=read(),x=read(); modify(p,x); printf("%lld\n",ans); } return 0; }
|