By lby学长 2025.7.13
讲课记录 in smsky Summer Camp
大步小步算法(Baby Step Giant Step) 用于求解如下问题:
求 t 满足 a t ≡ b ( m o d p ) ,其中 p 是质数。 求 t 满足 a^t ≡ b (\mod p) ,其中 p 是质数。 求t满足at≡b(modp),其中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;
}
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;
}
link
直接套用普通 BSGS 模板即可。
link
同 T1
。
link
板子 exBSGS 。
常出现在 组合计数
中。
把 n n n 个区分的球装入 m m m 个不区分的盒子,要求盒子非空,记为 S 2 ( n , m ) S2(n, m) S2(n,m) 或 { n m } {n\brace m} {mn}。
考虑第 n n n 个球,因为盒子不区分,可以新开一个盒子单独放,也可以插入到之前的一个盒子,那么:
S 2 ( n , m ) = S 2 ( n − 1 , m − 1 ) + m × S 2 ( 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 ( n m ) O(nm) O(nm) 的。
当然还可以归纳出通项公式,可以通过容斥推出 :
S 2 ( n , m ) = ∑ i = 0 m ( − 1 ) m − i × i n ( 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
注意只有 S 2 ( 0 , 0 ) = 1 S2(0,0)=1 S2(0,0)=1 ,其余的 S 2 ( i , 0 ) = 0 S2(i,0)=0 S2(i,0)=0。!!!
把 n n n 个区分的球装入 m m m 个不区分的盒子,要求盒子非空且盒子内形成圆排列,记为 S 1 ( n , m ) S1(n, m) S1(n,m) 或 [ n m ] {n\brack m} [mn]。
考虑第 n n n 个球,同样可以新开一个盒子,也可以插入之前的盒子,但是此时盒子内是圆排列,所以插入到某个盒子开头和末尾是等价的,就是说有 n − 1 n−1 n−1 种插法:
S 1 ( n , m ) = S 1 ( n − 1 , m − 1 ) + ( n − 1 ) × S 1 ( 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 ( n m ) O(nm) O(nm) 递推式!
m n = ∑ i = 0 n S 2 ( n , i ) × m i ‾ m^n=\sum\limits_{i=0}^{n} S2(n,i) \times m^{\underline{i}} mn=i=0∑nS2(n,i)×mi
其中: m i ‾ = C m i × i ! m^{\underline{i}}=C_m^i \times i! mi=Cmi×i!
解释: m n m^n mn 的意义为 n n n 个区分的球装入 m m m 个区分的盒子,那么先用 C m i C_m^i Cmi 选出有 i i i 个盒子非空(剩下 m − i m−i m−i 个空盒子),乘上 S 2 ( n , i ) S2(n, i) S2(n,i) 将球不区分地装入,最后乘上 i ! i! i! 把盒子区分。
link
题意
求: ∑ i = 1 n ( n i ) i k \sum\limits_{i=1}^{n} \tbinom{n}{i} i^k i=1∑n(in)ik
1 ≤ n ≤ 1 0 9 , 1 ≤ k ≤ 5000 1 \le n \le 10^9,1 \le k \le 5000 1≤n≤109,1≤k≤5000
思路
原式 = ∑ i = 1 n ( n i ) ∑ j = 1 k { k j } i j ‾ = ∑ j = 1 k { k j } j ! ∑ i = 1 n ( n i ) ( i j ) = ∑ j = 1 k { k j } j ! ∑ i = 1 n ( n j ) ( n − j i − j ) = ∑ j = 1 k { k j } j ! ( n j ) ∑ i = 0 n − j ( n − j i ) = ∑ j = 1 k { k j } j ! ( n j ) 2 n − 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
#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;
}
link
题意
求: ∑ j = 1 n w j ∑ i = 1 n i ( n − 1 i − 1 ) { n − i k − 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 = 1 n i ( n − 1 i − 1 ) { n − i k − 1 } = ∑ i = 1 n i ( n − 1 i − 1 ) ∑ j = 0 k − 1 ( − 1 ) k − 1 − j j n − i ( k − 1 − j ) ! j ! = ∑ j = 0 k − 1 ( − 1 ) k − 1 − j ( k − 1 − j ) ! j ! ∑ i = 1 n i ( n − 1 i − 1 ) j n − 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
考虑如何快速求解 i i i 这层循环:配平二项式,因为若没有 × i \times i ×i ,此式就为二项式反演了。
∑ i = 1 n i ( n − 1 i − 1 ) j n − i = ∑ i = 1 n ( i − 1 ) ( n − 1 i − 1 ) j n − i + ∑ i = 1 n ( n − 1 i − 1 ) j n − i = ( n − 1 ) ∑ i = 1 n ( 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 ) \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 = 0 k − 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
#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;
}
link
题意