初等数论Ⅱ
By lby学长 2025.7.13
讲课记录 in smsky Summer Camp
目录
- 大步小步算法(BSGS)
- 例题
- T1 [TJOI2007] 可爱的质数
- T2 [SDOI2011] 计算器
- T3 SPOJ3105 Mod
- Stirling 数
- 第二类 Stirling 数
- 第一类 Stirling 数
- Stirling 数 与 幂
- 例题
- T1 CF932E Team Work
- T2 CF961G Partitions
- T3 CF1278F Cards
大步小步算法(BSGS)
大步小步算法(Baby Step Giant Step) 用于求解如下问题:
求t满足at≡b(modp),其中p是质数。求 t 满足 a^t ≡ b (\mod p) ,其中 p 是质数。求t满足at≡b(modp),其中p是质数。
- 当 a,pa,pa,p 互质 时:
根据 欧拉定理 可知:aϕ(p)≡1(modp)a^{\phi(p)} ≡1 (\mod p)aϕ(p)≡1(modp)
于是便有:at≡atmodϕ(p)(modp)a^t≡ a^{t \mod \phi(p)} (\mod p)at≡atmodϕ(p)(modp)
所以 ttt 的取值范围为 [0,ϕ(p)−1][0,\phi(p)-1][0,ϕ(p)−1] ;
考虑把 111, aaa, a2a^2a2, …, aϕ(p)−1a^{ϕ(p)−1}aϕ(p)−1 依次排成一个圆,那么问题相当于问从起点要走几步能到达某个点。
运用类似分块的思想。枚举要走多少大格和多少小步。设 t=kx−yt=kx-yt=kx−y ,k=ϕ(p)k=\phi(p)k=ϕ(p) ,x∈[1,ϕ(p)],y∈[0,ϕ(p)−1]x \in [1,\phi(p)] ,y \in [0,\phi(p)-1]x∈[1,ϕ(p)],y∈[0,ϕ(p)−1]。
将原始代换得: akx−y≡b(modp)a^{kx-y}≡ b(\mod p)akx−y≡b(modp)
即为:
akx≡b×ay(modp)a^{kx}≡ b \times a^y(\mod p)akx≡b×ay(modp)
考虑将每个 yyy 对应的 b×ayb \times a^yb×ay 用 Hash表 存下来,枚举 xxx 找到最小的合法即可。
当然代码时由于 ϕ(p)\phi(p)ϕ(p) 不方便求,进而以 p\sqrt{p}p 来代替 ϕ(p)\phi(p)ϕ(p) ,保证 p≥ϕ(p)\sqrt{p} \ge \phi(p)p≥ϕ(p) 。
unordered_map<ll,ll>mp;
ll BSGS(ll a,ll b,ll p){a%=p,b%=p;if(!a&&!b) return 1;if(b==1) return 0;if(__gcd(a,p)!=1) return -1;mp.clear();int siz=sqrt(p); if(siz*siz!=p) siz++;mp[b]=0; ll bka=1;for(int i=1;i<=siz;i++){(b*=a)%=p; mp[b]=i;(bka*=a)%=p;}ll now=1,stp=0;for(int i=1;i<=siz;i++){(now*=bka)%=p;if(mp.count(now)) return 1ll*i*siz-mp[now];}return -1;
}
- a,pa,pa,p 不互质 时:
这就叫 扩展大步小步 。那就一直除 at≡b(modp)a^t ≡ b (\mod p)at≡b(modp) 中 aaa 和 ppp 的约数,直到互质为止。
例如: 设 g=gcd(a,p)g=gcd(a,p)g=gcd(a,p)
at≡b(modp)a^t ≡ b (\mod p)at≡b(modp)
ag×at−1≡bg(modpg)\frac{a}{g} \times a^{t-1} ≡ \frac{b}{g} (\mod \frac{p}{g})ga×at−1≡gb(modgp)
at−1≡bg×(ag)−1(modpg)a^{t-1} ≡ \frac{b}{g} \times (\frac{a}{g})^{-1}(\mod \frac{p}{g})at−1≡gb×(ga)−1(modgp)
可以发现,新的 aaa 不会变而 ppp 会,所以可能需要继续递归。如果 g∤bg \nmid bg∤b ,说明该方程无解。这个递归套用 Exgcd 即可。
void Exgcd(ll a,ll b,ll &x,ll &y){if(!b){x=1,y=0;return;}Exgcd(b,a%b,y,x); y-=a/b*x;
}unordered_map<ll,ll>mp;
ll BSGS(ll a,ll b,ll p){mp.clear();int siz=sqrt(p); if(siz*siz!=p) siz++;mp[b]=0; ll bka=1;for(int i=1;i<=siz;i++){(b*=a)%=p; mp[b]=i;(bka*=a)%=p;}ll now=1;for(int i=1;i<=siz;i++){(now*=bka)%=p;if(mp.count(now)) return 1ll*i*siz-mp[now];}return -1;
}ll ExBSGS(ll a,ll b,ll p){a%=p,b%=p;if(b==1||p==1) return 0;if(!a){if(!b) return 1;return -1;}ll gcd=__gcd(a,p),stp=0,ad=1;while(gcd!=1){if(b%gcd!=0) return -1;stp++,b/=gcd,p/=gcd,(ad*=a/gcd)%=p;gcd=__gcd(a,p);if(ad==b) return stp;}ll x=0,y=0; Exgcd(ad,p,x,y); x%=p,(x+=p)%=p;//!ad=x*b%p;ll stp2=BSGS(a,ad,p);if(stp2==-1) return -1;return stp+stp2;
}
例题
T1 [TJOI2007] 可爱的质数
link
直接套用普通 BSGS 模板即可。
T2 [SDOI2011] 计算器
link
同 T1
。
T3 SPOJ3105 Mod
link
板子 exBSGS 。
Stirling 数
常出现在 组合计数
中。
第二类 Stirling 数
把 nnn 个区分的球装入 mmm 个不区分的盒子,要求盒子非空,记为 S2(n,m)S2(n, m)S2(n,m) 或 {nm}{n\brace m}{mn}。
考虑第 nnn 个球,因为盒子不区分,可以新开一个盒子单独放,也可以插入到之前的一个盒子,那么:
S2(n,m)=S2(n−1,m−1)+m×S2(n−1,m)\rm S2(n, m) = S2(n−1, m−1) + m \times S2(n−1, m)S2(n,m)=S2(n−1,m−1)+m×S2(n−1,m)
这个显然是 O(nm)O(nm)O(nm) 的。
当然还可以归纳出通项公式,可以通过容斥推出 :
S2(n,m)=∑i=0m(−1)m−i×in(m−i)!i!\rm S2(n, m) = \sum\limits_{i=0}^{m}(-1)^{m-i} \times \frac{i^n}{(m-i)!i!}S2(n,m)=i=0∑m(−1)m−i×(m−i)!i!in
注意只有 S2(0,0)=1S2(0,0)=1S2(0,0)=1 ,其余的 S2(i,0)=0S2(i,0)=0S2(i,0)=0。!!!
第一类 Stirling 数
把 nnn 个区分的球装入 mmm 个不区分的盒子,要求盒子非空且盒子内形成圆排列,记为 S1(n,m)S1(n, m)S1(n,m) 或 [nm]{n\brack m}[mn]。
考虑第 nnn 个球,同样可以新开一个盒子,也可以插入之前的盒子,但是此时盒子内是圆排列,所以插入到某个盒子开头和末尾是等价的,就是说有 n−1n−1n−1 种插法:
S1(n,m)=S1(n−1,m−1)+(n−1)×S1(n−1,m)S1(n, m) = S1(n−1, m−1) + (n − 1) \times S1(n−1,m)S1(n,m)=S1(n−1,m−1)+(n−1)×S1(n−1,m)
注意第一类斯特林数只有 O(nm)O(nm)O(nm) 递推式!
Stirling 数 与 幂
mn=∑i=0nS2(n,i)×mi‾m^n=\sum\limits_{i=0}^{n} S2(n,i) \times m^{\underline{i}}mn=i=0∑nS2(n,i)×mi
其中:mi‾=Cmi×i!m^{\underline{i}}=C_m^i \times i!mi=Cmi×i!
解释:mnm^nmn 的意义为 nnn 个区分的球装入 mmm 个区分的盒子,那么先用 CmiC_m^iCmi 选出有 iii 个盒子非空(剩下 m−im−im−i 个空盒子),乘上 S2(n,i)S2(n, i)S2(n,i) 将球不区分地装入,最后乘上 i!i!i! 把盒子区分。
例题
T1 CF932E Team Work
link
题意
求:∑i=1n(ni)ik\sum\limits_{i=1}^{n} \tbinom{n}{i} i^ki=1∑n(in)ik
1≤n≤109,1≤k≤50001 \le n \le 10^9,1 \le k \le 50001≤n≤109,1≤k≤5000
思路
原式=∑i=1n(ni)∑j=1k{kj}ij‾=∑j=1k{kj}j!∑i=1n(ni)(ij)=∑j=1k{kj}j!∑i=1n(nj)(n−ji−j)=∑j=1k{kj}j!(nj)∑i=0n−j(n−ji)=∑j=1k{kj}j!(nj)2n−j\begin{aligned} 原式&= \sum\limits_{i=1}^{n} \binom{n}{i} \sum\limits_{j=1}^{k} {k\brace j} i^{\underline{j}}\\ &= \sum\limits_{j=1}^{k}{k\brace j}j!\sum\limits_{i=1}^{n} \binom{n}{i} \binom{i}{j}\\ &= \sum\limits_{j=1}^{k}{k\brace j}j!\sum\limits_{i=1}^{n} \binom{n}{j} \binom{n-j}{i-j} \\ &=\sum\limits_{j=1}^{k}{k\brace j}j! \binom{n}{j} \sum\limits_{i=0}^{n-j} \binom{n-j}{i}\\ &=\sum\limits_{j=1}^{k}{k\brace j}j! \binom{n}{j} 2^{n-j} \end{aligned}原式=i=1∑n(in)j=1∑k{jk}ij=j=1∑k{jk}j!i=1∑n(in)(ji)=j=1∑k{jk}j!i=1∑n(jn)(i−jn−j)=j=1∑k{jk}j!(jn)i=0∑n−j(in−j)=j=1∑k{jk}j!(jn)2n−j
代码
#pragma GCC optimize(3)
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxk=5005,mod=1e9+7;ll Pow_(ll x,ll y){ll s=1;while(y){if(y&1) (s*=x)%=mod;(x*=x)%=mod;y>>=1;}return s;
}ll sl[maxk][maxk];
int main(){int n,k; cin>>n>>k;sl[0][0]=1;for(int i=1;i<=k;i++)for(int j=1;j<=i;j++)sl[i][j]=(sl[i-1][j-1]+j*sl[i-1][j]%mod)%mod;ll ans=0,tmp=1,pw2=Pow_(2,n),inv2=Pow_(2,mod-2);for(int i=1;i<=k;i++){(tmp*=(n-i+1))%=mod;(pw2*=inv2)%=mod;(ans+=sl[k][i]*tmp%mod*pw2%mod)%=mod;} cout<<ans;return 0;
}
T2 CF961G Partitions
link
题意
求:∑j=1nwj∑i=1ni(n−1i−1){n−ik−1}\sum\limits_{j=1}^{n} w_j \sum\limits_{i=1}^{n} i\binom{n-1}{i-1} {n-i\brace k-1}j=1∑nwji=1∑ni(i−1n−1){k−1n−i}
思路
∑i=1ni(n−1i−1){n−ik−1}=∑i=1ni(n−1i−1)∑j=0k−1(−1)k−1−jjn−i(k−1−j)!j!=∑j=0k−1(−1)k−1−j(k−1−j)!j!∑i=1ni(n−1i−1)jn−i\begin{aligned} \sum\limits_{i=1}^{n} i\binom{n-1}{i-1} {n-i\brace k-1}&=\sum\limits_{i=1}^{n} i\binom{n-1}{i-1} \sum\limits_{j=0}^{k-1}(-1)^{k-1-j}\frac{j^{n-i}}{(k-1-j)!j!}\\ &=\sum\limits_{j=0}^{k-1}\frac{(-1)^{k-1-j}}{(k-1-j)!j!}\sum\limits_{i=1}^{n} i\binom{n-1}{i-1} j^{n-i} \end{aligned}i=1∑ni(i−1n−1){k−1n−i}=i=1∑ni(i−1n−1)j=0∑k−1(−1)k−1−j(k−1−j)!j!jn−i=j=0∑k−1(k−1−j)!j!(−1)k−1−ji=1∑ni(i−1n−1)jn−i
考虑如何快速求解 iii 这层循环:配平二项式,因为若没有 ×i\times i×i ,此式就为二项式反演了。
∑i=1ni(n−1i−1)jn−i=∑i=1n(i−1)(n−1i−1)jn−i+∑i=1n(n−1i−1)jn−i=(n−1)∑i=1n(n−2i−2)jn−i+(j+1)n−1=(n−1)(j+1)n−2+(j+1)n−1=(j+1)n−2(n+j)\begin{aligned} \sum\limits_{i=1}^{n} i\binom{n-1}{i-1} j^{n-i} &=\sum\limits_{i=1}^{n} (i-1)\binom{n-1}{i-1} j^{n-i}+\sum\limits_{i=1}^{n} \binom{n-1}{i-1} j^{n-i}\\ &=(n-1) \sum\limits_{i=1}^{n} \binom{n-2}{i-2} j^{n-i}+(j+1)^{n-1}\\ &=(n-1)(j+1)^{n-2}+(j+1)^{n-1}\\ &=(j+1)^{n-2}(n+j) \end{aligned}i=1∑ni(i−1n−1)jn−i=i=1∑n(i−1)(i−1n−1)jn−i+i=1∑n(i−1n−1)jn−i=(n−1)i=1∑n(i−2n−2)jn−i+(j+1)n−1=(n−1)(j+1)n−2+(j+1)n−1=(j+1)n−2(n+j)
所以原式变成了:
∑j=0k−1(−1)k−1−j(k−1−j)!j!(j+1)n−2(n+j)\sum\limits_{j=0}^{k-1}\frac{(-1)^{k-1-j}}{(k-1-j)!j!}(j+1)^{n-2}(n+j)j=0∑k−1(k−1−j)!j!(−1)k−1−j(j+1)n−2(n+j)
代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=2e5+5,mod=1e9+7;ll Pow_(ll x,ll y){ll s=1;while(y){if(y&1) (s*=x)%=mod;(x*=x)%=mod;y>>=1;}return s;
}ll a[maxn],fac[maxn],inv_fac[maxn];
int main(){int n,k; cin>>n>>k;fac[0]=inv_fac[0]=1;for(int i=1;i<=k;i++){fac[i]=fac[i-1]*i%mod;inv_fac[i]=Pow_(fac[i],mod-2);}ll sum=0;for(int i=1;i<=n;i++){cin>>a[i];(sum+=a[i])%=mod;}if(n==1){if(k==1) cout<<a[1];else cout<<0;return 0;}ll ans=0;for(int i=0;i<k;i++){if((k-1-i)&1) ans-=Pow_(i+1,n-2)*(n+i)%mod*inv_fac[k-1-i]%mod*inv_fac[i]%mod,(ans+=mod)%=mod;else (ans+=Pow_(i+1,n-2)*(n+i)%mod*inv_fac[k-1-i]%mod*inv_fac[i]%mod)%=mod;}cout<<sum*ans%mod;return 0;
}
T3 CF1278F Cards
link
题意