山东集训笔记
4.29
- 访问数组某一位后其后面若干位会进入缓存,缓存运行速度较快。因此多维数组可以通过优化循环顺序提高运行速度。
- ::a 可用来访问全局变量。
- 从
到 走 步的方案数可用矩阵加速。 ,a表示邻接矩阵。具体见图:
4.30
- 运用逆元对除法做模运算(适用范围:
互质)。
若p为质数,运用费马小定理:
否则,运用欧拉定理:
- 求
。(类似容斥)
喜报:扩展欧几里得算法全面碾了费马小定理,时间复杂度相同,但扩欧不要求互素。
根据逆元定义可得
这就可以用扩欧求解了。
-
求逆元
阶乘和阶乘逆元预处理,然后可以将 的逆元全部求出(约分)。
代码:
fac[0]=1; for(int i=1;i<=n;++i) fac[i]=1ll*fac[i-1]*i%p; ifac[n]=ksm(fac[n],p-2,p); for(int i=n-1;i>=0;--i) ifac[i]=1ll*ifac[i+1]*(i+1)%p; for(int i=1;i<=n;++i) inv[i]=1ll*fac[i-1]*ifac[i]%p;
- 递推。
代码:
inv[1]=1; for(int i=2;i<=n;++i) { int k=p/i,r=p%i; inv[i]=1ll*(p-k)*inv[r]%p;//p-k同余于-k }
- Miller-Rabin 素性测试
若
代码:
点击查看代码
bool Miller-Rabin(int n,int a) { int d=i-1,r=0; while(!d%2) d/=2,++r; int x=ksm(a,d,n); if(x==1) return 1; for(int i=0;i<r;++i) { if(x==n-1) return 1; x=1ll*x*x%n; } return 0; } bool is_prime(int now)//写法1 { for(int i=1;i<=50;++i) { int a=rand()%(n-1)+1; if(!now%a) return 0; } return 1; } bool is_prime(int now)//写法2(成功率较高) { if(now<2) return 0; for(int i=1;i<8;++i) { if(now==prime_list[i]) return 1; if(!now%prime_list[i]) return 0; if(!Miller-Rabin(now,prime_list[i]%n)) return 0; } return 1; }
int exgcd(int a,int b,int &x,int &y) { if(!b) { x=1,y=0; return a; } int g=exgcd(b,a%b,x,y); int temp=x; x=y,y=temp-a/b*y; return g; }
来自 zr 的优化写法:
int exgcd(int a,int b,int &x,int &y) { if(!b) { x=1,y=0; return a; } int g=exgcd(b,a%b,y,x); return y-=a/b*x,g; }
- 中国剩余定理
显然,两个同余方程可解出一个同余方程。暴力做法是进行枚举,每次加 (但这个暴力不好卡 qaq)
可以发现这样就和扩展欧几里得算法类似,就可以写出如下代码(学长的代码挂掉了,放的是我在模板题解中学习的方法):
点击查看代码
#include<bits/stdc++.h> #define ll long long #define ld long double using namespace std; ll n,p[100005],a[100005]; ll exgcd(ll a,ll b,ll &x,ll &y) { if(!b){ x=1,y=0; return a; } ll g=exgcd(b,a%b,x,y); ll temp=x; x=y,y=temp-a/b*y; return g; } ll mul(ll a,ll b,ll md) { ll ans=0; while(b) { if(b&1) ans=(ans+a)%md; a=(a+a)%md; b>>=1; } return ans; } ll EXCRT() { ll x,y; ll ans=a[1],P=p[1]; for(int i=2;i<=n;++i) { ll B=p[i],C=(a[i]-ans%B+B)%B; ll g=exgcd(P,B,x,y),temp=B/g; if(C%g) return -1; x=mul(x,C/g,temp)/*a2-a1并不一定是P,B的gcd,因此需要乘(a2-a1)/gcd(P,B)*/,ans+=x*P,P*=temp,ans=(ans%P+P)%P; } return (ans%P+P)%P; } int main() { ios_base::sync_with_stdio(false); cin.tie(NULL); cout.tie(NULL); cin>>n; for(int i=1;i<=n;++i) cin>>p[i]>>a[i]; cout<<EXCRT()<<endl; return 0; }
- 筛法
- 埃氏筛
直接上代码:
for(int i=2;i<=n;++i) for(int j=i<<1;j<=n;j+=i) not_prime[b]=1;
复杂度
稍加优化:
for(int i=2;i<=n;++i) if(!not_prome[i]) for(int j=i<<1;j<=n;j+=i) not_prime[j]=1;
复杂度
- 欧拉筛
按照上面的思想,每个数都会被它的许多质因子筛一次,这就会造成一定浪费。
代码:
for(int i=2;i<=n;++i) { if(!not_prime[i]) prime[++cnt]=i; for(int j=1;j<=cnt;++j) { int x=prime[j]*i; if(x>n) break; not_prime[x]=1; if(!(i%prime[j])) break;//保证只被最小的素数筛 } }
- 积性函数:对于互素的
, .
完全积性函数:对于任意的
若
递推求
for(int i=2;i<=n;++i) { if(!not_prime[i]) prime[++cnt]=i; for(int j=1;j<=cnt;++j) { int x=prime[j]*i; if(x>n) break; not_prime[x]=1,phi[x]=(prime[j]-1)*phi[i]; if(!i%prime[j]) { phi[x]=phi[i]*prime[j]; break; } } }
5.1
- 北上广深(BSGS)
给定质数
如果将
代码:
bool solve(int a,int b,int p) { int s=sqrt(p),v=1; for(int i=0;i<=s;++i) mp[v]=i,v=1ll*v*a%p; for(int i=0;i<=pow(p,0.5);++i) { int c=1ll*b*ksm(ksm(a,i*s),p-2)%p; if(mp.count(c)) return cout<<mp[c]+i*s,0; } return 1; }
复杂度
- 组合数学
杨辉三角,递推,加法原理,乘法原理都会hh。
- 选任意多,共有
种方案。 - 对于
,
证明:
多次枚举,可以发现,这些可以刚好消掉,不严谨地证毕。
PS:因为
- 二项式定理
真没想到就是这个
与递推结合一下,可得展开
由前一个式子到后一个式子可以理解为将连续加和反向,写可以设
- 卢卡斯定理
适用于组合数取模,模数为质数。将 转化为 进制,则:
其实最原始的公式是这样的,利用递归:
浅浅证明一下:
首先有一个前置:
根据二项式定理,前式可以写成如下形式:
对于质数
至此,该前置式子已证毕。
接下来证明定理。
设
对于
上式
二项式展开,试图开卷,可以看出前一个式子只能得到幂次大于等于
证毕。
代码(因为我的不理解,还以为代码又挂了,对不起,贴一下模板):
点击查看代码
#include<bits/stdc++.h> #define ld long double #define ll long long using namespace std; int t,p,a[100005],b[100005],tail1,tail2,frac[100005]; void first(){ frac[0]=1; for(int i=1;i<=p;++i) frac[i]=1ll*frac[i-1]*i%p; } int ksm(int x,int mx) { int ans=1; while(mx) { if(mx&1) ans=1ll*ans*x%p; x=1ll*x*x%p; mx>>=1; } return ans; } int C(int x,int y) { if(y>x) return 0; return (1ll*frac[x]*ksm(frac[y],p-2)%p*ksm(frac[x-y],p-2)%p); } int Lucas(int n,int m) { memset(a,0,sizeof(a)),memset(b,0,sizeof(b)); tail1=tail2=0; while(n) a[++tail1]=n%p,n/=p; while(m) b[++tail2]=m%p,m/=p; int ans=1; for(int i=1;i<=max(tail1,tail2);++i) ans=1ll*ans*C(a[i],b[i])%p; return ans; } int n,m; int main() { ios_base::sync_with_stdio(false); cin.tie(NULL); cout.tie(NULL); cin>>t; while(t--) { cin>>n>>m>>p; first(); cout<<((p==1)?0:Lucas(n+m,n))<<"\n"; } return 0; }
exlucas 不咕哩。
首先将
之后就可以用 excrt 求解。
很显然,阶乘的逆元不能直接求,因为不一定互质。
那就构造一下,将因子
现在等价于求
其中,
即将所有小于
显然,剩余部分会构成一个循环节,每
然后就是求其中的
假设
代码:
点击查看代码
#include<bits/stdc++.h> #define ld long double #define ll long long using namespace std; ll n,m,p,A[100005],pr[100005],cnt; ll exgcd(ll a,ll b,ll &x,ll &y) { if(!b) return x=1,y=0,a; ll g=exgcd(b,a%b,x,y); ll temp=x; x=y,y=temp-a/b*y; return g; } ll inv(ll x,ll pi) { ll a,b; exgcd(x,pi,a,b); return (a+pi)%pi; } ll ksm(ll x,ll mx,ll md) { ll ans=1; x%=md; while(mx) { if(mx&1) ans=ans*x%md; x=x*x%md; mx>>=1; } return ans; } ll mul(ll x,ll mx,ll md) { ll ans=0; while(mx) { if(mx&1) (ans+=x)%=md; (x+=x)%=md; mx>>=1; } return ans; } ll fac(ll x,ll pi,ll tol) { if(x==0) return 1; ll cir=1,rem=1; for(ll i=1;i<=tol;++i) if(i%pi) cir=cir*i%tol; cir=ksm(cir,x/tol,tol); for(ll i=tol*(x/tol);i<=x;++i) if(i%pi) rem=rem*(i%tol)%tol; return fac(x/pi,pi,tol)*cir%tol*rem%tol; } ll sum(ll x,ll pi){ return x<pi?0:sum(x/pi,pi)+x/pi; } ll C(ll pi,ll tol) { ll k1=fac(n,pi,tol),k2=inv(fac(m,pi,tol),tol),k3=inv(fac(n-m,pi,tol),tol),k4=ksm(pi,sum(n,pi)-sum(m,pi)-sum(n-m,pi),tol); return k1*k2%tol*k3%tol*k4%tol; } ll excrt() { ll x,y; ll ans=A[1],P=pr[1]; for(int i=2;i<=cnt;++i) { ll B=pr[i],C=(A[i]-ans%B+B)%B; ll g=exgcd(P,B,x,y),temp=B/g; x=mul(x,C/g,temp),ans+=x*P,P*=temp,ans=(ans%P+P)%P; } return (ans%P+P)%P; } ll exlucas() { ll P=p; for(ll i=2;i*i<=p;++i) if(!(P%i)) { ll temp=1; while(!(P%i)) temp*=i,P/=i; pr[++cnt]=temp; A[cnt]=C(i,temp); } if(P!=1) pr[++cnt]=P,A[cnt]=C(P,P); return excrt(); } int main() { ios_base::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr); cin>>n>>m>>p; cout<<exlucas()<<"\n"; return 0; }
如P3746,首先运用二项式定理。
将
- 抽屉原理
模型:将
- 容斥原理
很容易理解的一个东西,公式如下。
5.2
- 可以将较强的要求拆解,转换为较弱的条件,再利用容斥解决。
- 高斯消元
就这篇博客放这儿你敢说不会?
就这题你怎么过的
竟然不讲约旦!
- 计算逆矩阵
只有对角线为
交换单位矩阵第
改变单位矩阵第
将原矩阵与单位矩阵一起进行高斯消元,可求得原矩阵逆矩阵。
放一下模板代码:
点击查看代码
#include<bits/stdc++.h> #define ll long long #define ld long double using namespace std; const int p=1e9+7; ll n,a[405][805]; ll ksm(ll x,ll mx) { ll ans=1; while(mx) { if(mx&1) ans=ans*x%p; x=x*x%p; mx>>=1; } return ans%p; } void g_j() { for(int i=1;i<=n;++i) { int temp=i; for(int j=i+1;j<=n;++j) if(a[j][i]>a[temp][i]) temp=j; if(temp!=i) for(int j=1;j<=(n<<1);++j) swap(a[i][j],a[temp][j]); if(!a[i][i]){ cout<<"No Solution"<<endl; return; } ll w=ksm(a[i][i],p-2); for(int j=1;j<=n;++j) if(j!=i) { ll r=a[j][i]*w%p; for(int k=i+1;k<=(n<<1);++k) a[j][k]=((a[j][k]-a[i][k]*r)%p+p)%p; } for(int j=1;j<=(n<<1);++j) a[i][j]=a[i][j]*w%p; } for(int i=1;i<=n;++i) { for(int j=n+1;j<=(n<<1);++j) cout<<a[i][j]<<' '; cout<<"\n"; } } int main() { ios_base::sync_with_stdio(false); cin.tie(NULL); cout.tie(NULL); cin>>n; for(int i=1;i<=n;++i) { a[i][i+n]=1; for(int j=1;j<=n;++j) cin>>a[i][j]; } g_j(); return 0; }
你看看你题单里的两紫两黑。
- 求和变形
- 换元法
见下:
结课啦,芜湖(zhx学长性格好好啊)。
本文作者:cat_and_code
本文链接:https://www.cnblogs.com/cat-and-code/p/17478406.html
版权声明:本作品采用署名—非商业性使用—相同方式共享 4.0 协议国际版许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步