【模板篇】数论大杂烩~
离省选已经不剩几天了, 然而自己还是啥也不会, 所以就临阵磨枪刷一刷板子, 万一用上了就赚到了~~
这一篇就写写数论?(希望只考到自己打的板子然而这是不可能的←_←
尽量不压行吧, 能写得好懂点就写得好懂一点吧... (为了省事就全开long long了哈, 卡常题请谨慎复制...)
里面枚举的内容可能没啥规律, 想起啥写啥, 应该会很乱...
先是应该是不用写的gcd:
LL gcd(LL a,LL b){
if(!b) return a;
return gcd(b,a%b);
}
然后是exgcd:
void exgcd(LL a,LL b,LL &x,LL &y){
if(!b) x=1,y=0;
else exgcd(b,a%b,y,x),y-=(a/b)*x;
}
快速幂也是炒鸡常用的:
LL qpow(LL a,LL b,LL p,LL s=1){
for(;b;b>>=1,a=a*a%p)
if(b&1) s=s*a%p;
return s;
}
然后是跟质数或者辣鸡反演有关的题里几乎都要用到的线筛(这玩意我还日常打不对的说...)
筛质数的同时写一下筛\(\mu, \varphi, \sigma_0,\sigma_1\)这些函数(打表找规律的积性函数就不写了也写不完)
/*
pr质数数组 tot质数个数 mu莫比乌斯函数 phi欧拉函数 d约数个数 sd约数和
fd 筛约数个数用的辅助数组(最小质因子的个数) fsd 筛约数和用的辅助数组
(最小质因子等比数列的前缀和,即 (1+p1+p1^2+p1^3+...+p1^k1))
*/
void shai(){
np[1]=mu[1]=phi[1]=d[1]=sd[1]=1;
for(int i=2,k;i<=n;++i){
if(!np[i]){
pr[++tot]=i; mu[i]=-1; phi[i]=i-1;
fd[i]=1; d[i]=2; fsd[i]=sd[i]=i+1;
}
for(int j=1;j<=tot&&(k=i*pr[j])<=n;++j){ np[k]=1;
if(i%pr[j]==0){
mu[k]=0; phi[k]=phi[i]*pr[j];
fd[k]=fd[i]+1; d[k]=d[i]/(fd[i]+1)*(fd[i]+2);
fsd[k]=fd[i]*pr[j]+1;
sd[k]=sd[i]/fsd[i]*fsd[k];
break;
}
mu[k]=-mu[i]; phi[k]=phi[i]*(pr[j]-1);
fd[k]=1; d[k]=d[i]*2; fsd[k]=pr[j]+1;
sd[k]=sd[i]*sd[pr[j]];
}
}
}
Emmmm写个线筛写了好久最后果然还是出错了..
逆元相关:
然后是两种求\(a\)在\(mod\ p\)逆元的方法:
// 费马小定理的a^p-2代码就不写了_(:з」∠)_
// 另一种就是利用exgcd:
LL inv(LL a,LL p){
if(!a) return 0;
LL x,y; exgcd(a,p,x,y);
return (x+p)%p;
}
线性求出所有逆元:
inv[1]=1;
for(int i=2;i<p;++i)
inv[i]=(p-(p/i))*inv[p%i]%p;
O(1)快速乘(损精度):
LL multi(Ll a,LL b){
return (a*b-(LL)((long double)a*b/p)*p+p)%p;
}
然后是一发Lucas定理:
// 这里默认阶乘和阶乘逆元已经预处理好分别放在fac和inv数组里了~
LL lucas(LL n,LL m,LL p){
if(n<m) return 0;
if(n<p) return fac[x]*inv[y]*inv[x-y];
return lucas(n/p,m/p,p)*lucas(n%p,m%p,p);
}
再来一波CRT(中国剩余定理)
//假设模数存在p数组里, 余数存在c数组里, 一共有n个式子, 模数的总乘积是P.
void CRT(LL ans=0){
for(int i=0;i<n;++i)
ans=(ans+c[i]*(P/p[i])%P*inv(P/p[i],p[i])%P)%P;
return ans;
}
突然发现自己还没写过扩展CRT哇, 赶紧新写一发~
int exCRT(){
for(int i=2;i<=n;++i){
p1=p[i-1],p2=p[i],c1=c[i-1],c2=c[i];
g=gcd(p1,p2);
if((c2-c1)%g) return -1;
p[i]=p1*p2/t;
c[i]=inv(p1/t,p2/t)*((c2-c1)/t)%(p2/t)*p1+c1;
c[i]=(c[i]%p[i]+p[i])%p[i];
}
return c[n];
}
然后写一发bsgs~叫拔山盖世的都是邪教!北上广深才是王道!
map<LL,int> mmp;
LL BSGS(LL y,LL z,LL p){ mmp.clear();
LL m=sqrt(p)+1; po=qpow(y,m,p);
for(LL j=0;j<=m;++j){
v[z]=j; z=z*y%c;
} z=1;
for(LL i=1;i<=m;++i){
z=z*po%p;
if(v[x]) return i*m-v[x];
}
return -1;
}
然后就是组合数题的boss了(仅代表个人观点), 非常神的 扩展Lucas~~~~
去年这时候学的, 现在几乎忘得一干二净
刚才打了一遍板子, 然而还是没有背过...
LL mul(LL n,LL pi,LL pk){
if(!n) return 1; LL ans=1;
if(n/pk){
for(LL i=2;i<=pk;++i)
if(i%pi) ans=ans*i%pk;
ans=qpow(ans,n/pk,pk);
}
for(LL i=2;i<=n%pk;++i)
if(i%pi) ans=ans*i%pk;
return ans*mul(n/pi,pi,pk)%pk;
}
LL C(LL n,LL m,LL p,LL pi,LL pk){
if(m>n) return 0;
LL a=mul(n,pi,pk),b=mul(m,pi,pk),c=mul(n-m,pi,pk),k=0,ans;
for(LL i=n;i;i/=pi) k+=i/pi;
for(LL i=m;i;i/=pi) k-=i/pi;
for(LL i=n-m;i;i/=pi) k-=i/pi;
ans=a*inv(b,pk)%pk*inv(c,pk)%pk*qpow(pi,k,pk)%pk;
return ans*(p/pk)%p*inv(p/pk,pk)%p;
}
int main(){
LL n,m,p,ans=0; scanf("%lld%lld%lld",&n,&m,&p); n+=m;
for(LL x=p,i=2;i<=p;++i)
if(x%i==0){
LL pk=1;
while(x%i==0) pk*=i,x/=i;
ans=(ans+::C(n, m, p, i, pk))%p;
}
printf("%lld\n",ans);
}
再来补充一些反演相关:
最基本的枚举除法
for(int i=1,last;i<=n;i=last+1){
last=n/(n/i);
// 然后处理的是关于last和i-1的式子..(一般就是前缀和相减..)
}
公式就记这几个其他常见的都可以推...
\[\mu*1=\epsilon \\
\varphi*1=n \\
id_k*1=\sigma_k
\]
套路性的东西:
\[\sum_{i=1}^n\sum_{j=1}^m[gcd(i,j)=1]=\sum_{d=1}^n\mu(d)\left\lfloor\frac nd\right\rfloor\left\lfloor\frac md\right\rfloor \\
\sum_{i=1}^n\sum_{j=1}^n[gcd(i,j)=1]=2*\sum_{d=1}^n\varphi(d)-1
\]
杜教筛的模板(舒老师说这玩意卵用没有但我觉得很有用哒(肯定是我太弱了)):
\[要求出f(x)的前缀和s_f(x), 我们要找到一个合适的函数g(x), 满足g(x)和(f*g)(x)的前缀和都易求(一般是要求O(1)). \\
令s_{f*g}(x)表示(f*g)x的前缀和, 那么有: \\
s_f(x)=\frac{s_{f*g}(x)-\sum_{i=2}^ns_f(\left\lfloor\frac ni\right\rfloor)g(i)}{g(1)} (当然正常来说g(1)=1就不用除辣)\\
然后我们枚举除法对上式整除分块递归处理s_f(\left\lfloor\frac ni\right\rfloor)这个部分就可以了~
\]
于是附上几个常见的易求前缀和的函数的求和公式:
\[\sum_{i=1}^n1(i)=n \\
\sum_{i=1}^n\epsilon(i)=1 \\
\sum_{i=1}^nid(i)=\frac{n(n+1)}2 \\
\sum_{i=1}^nid_2(i)=\frac{n(n+1)(2n+1)}6 \\
\sum_{i=1}^nid_3(i)=(\frac{n(n+1)}2)^2
\]
然后就是一些注意事项:
- \(10^{10}\)范围杜教筛筛\(O(n^\frac 23)\)大约是\(4.7*10^6\), 所以500W一般可以, 不过空间允许的话再筛大一点在1e7左右会更好~
- \(10^{10}*{10^9+7}\)有几率会爆long long, 虽然情况比较少但我相信足够毒瘤的出题人是可以有足够的时间和耐心搞出来hack数据的..., 所以保险起见要开unsigned long long...
- long long计算非常慢, 能用int的地方尽量用int, 当然那就要注意强转long long步步取模(少一个都有可能炸←_←)的问题了~
- 枚举因子的时候最好把设\(i=td\)写出来防止把应该化的\(t\)丢掉(可以适当找下规律验证化的式子对否)..
- 大约就这样吧, 说太多自己也是记不住的...