数论小桶

【前言】

由两道题引领的数论小全家桶(雾)。

  1. Devu and Flowers
  2. 古代猪文

显然一篇文章无法涵盖数论全部内容,本文大概涵盖的内容有:

  1. 欧拉定理 & 费马小定理。
  2. Bézout 定理 & 扩展欧几里得算法。
  3. 线性同余方程 & 乘法逆元。
  4. 中国剩余定理 & 扩展。
  5. 卢卡斯定理。

本文将逐一简略介绍,且基本不涉及定理的证明。

【前置芝士】

【欧拉定理 & 费马小定理】

费马小定理:

\(p\) 为质数,\(\gcd(a,p)=1\),则 \(a^{p-1}\equiv 1\pmod p\)

同时对于任意整数 \(a\),有 \(a^p\equiv a\pmod p\)

欧拉定理:

\(\gcd(a,p)=1\),则有 \(a^{\phi(p)}=1\pmod p\)

扩展欧拉定理:

\(a^b~\equiv~\begin{cases}a^{b~Mod~\phi(p)} &\gcd(a,p)~=~1 \\a^b &\gcd(a,p)~\neq~1~\land~b~<~\phi(p) \\ a^{b~Mod~\phi(p)~+~\phi(p)} &\gcd(a,p)~\neq~1~\land~b~\geq~\phi(p)\end{cases}\pmod p\)

严谨的证明

应用:

  1. 利用费马小定理,可以快速求出模数 \(p\) 为质数时,某个数 \(a\) 的乘法逆元为 \(a^{p-2}\)
  2. 利用扩展欧拉定理,可以在幂的指数很大时,快速缩小数据范围。

注意:费马小定理使用的前提是 \(\gcd(a,p)=1\)

【Bézout 定理 & 扩展欧几里得算法】

Bézout 定理:

\(a,b\) 均为整数且 \(a\times b\neq 0\),那么一定存在整数 \(x,y\) ,使得 \(ax+by=\gcd(a,b)\)

证明用到欧几里得算法(辗转相除法)求最小公倍数,同时还得出了扩展欧几里得算法:

LL Exgcd(LL a, LL b, LL &x, LL &y){
	if(!b) {x=1, y=0; return a;}
	LL d = Exgcd(b, a % b, x, y);
	LL z = x; x = y; y = z - y * (a / b);
	return d;
}

应用:Bézout 定理是扩展欧几里得算法的理论依据,而后者的运用十分广泛。(见后)

【线性同余方程 & 乘法逆元】

求解线性同余方程 \(ax\equiv b\pmod p\)

相当于求解方程 \(ax+py=b\)

不难利用 exgcd 求出 \(ax+py=\gcd(a,p)\) 的一个解 \(x_0\)

那么原同余方程的一个解就是 \(x_1=x_0/\gcd(a,p)\times b\)

原方程的通解就是与 \(\forall x,x\equiv x_1 \pmod {p/\gcd(a,p)}\)

求整数 \(a\)\(\mod p\) 意义下的乘法逆元。

  1. \(p\) 为质数且 \(\gcd(a,p)=1\)\({\rm inv}(a)=a^{p-2} \pmod p\)
  2. 对于更一般的情况直接求解线性同余方程 \(a\times x\equiv 1\pmod p\)

不难发现,单次求逆元的复杂度为 \(O(\log n)\),但或许我们还需要一次性求出 \(n\) 个数的逆元。(栗子

那么我们可以试着推一下方程:

求出 \(1\sim n\)\({\rm mod\ p}\) 意义下的逆元。

首先有 \(1^{-1}\equiv 1\pmod p\)

对于某个数 \(i\),设 \(p=k\times i+r\),那么显然:

\[k\times i+r\equiv 0\pmod p \]

\[k\times r^{-1}+i^{-1}\equiv 0\pmod p \]

\[i^{-1}\equiv -k\times r^{-1}\pmod p \]

\[i^{-1}\equiv -\lfloor\frac{p}{i}\rfloor\times (p\ {\rm mod}\ i)^{-1} \]

不难写出代码:

inv[1] = 1;
for(int i=2; i<=n; i++)
	inv[i] = 1LL * (p - p / i) * inv[p % i] % p;

还有一个线性求阶乘逆元的方法:(根据逆元的定义即可简单推出)

inv[n] = Pow(fac[n], MOD - 2);
for(int i=n-1; i>=1; i--)
    inv[i] = inv[i + 1] * (i + 1) % MOD;

它们本身就是上述算法的运用。

【中国剩余定理 & 扩展】

求解同余方程:

\[\left\{\begin{aligned}x\equiv\ a_1\pmod {m_1} \quad\\ x\equiv\ a_2\pmod {m_2} \quad\\ x\equiv\ a_3\pmod {m_3} \quad\\ ...\quad\\x\equiv\ a_n\pmod {m_n} \quad\end{aligned}\right. \]

保证所有 \(m_i\) 两两互质。

中国剩余定理:

\(M=\Pi_{i=1}^k m_i\)\(M_i=M/m_i\)

\[x_0=\sum_{i=1}^n a_iM_i\times inv(m_i,M_i) \]

通解为 \(\forall x=x_0+kM(k\in \mathbb{Z})\)

如果不保证所有 \(m_i\) 两两互质,同样有办法求解。

扩展中国剩余定理:模板

剖析原定理本质,其核心部分是:每次尝试构造一个数,使得这个数以某种方式融入答案后保证只对当前的 \((a_i,m_i)\) 有影响,同时保证 \(x\equiv a_i\pmod {m_i}\)

利用这个思想,我们不难思考出以下算法:

假设已经求出了前 \(k-1\) 个方程的解为 \(x\),记 \(m={\rm lcm}(m_1,m_2,...,m_{k-1})\)

显然 \(x+i\times m(i\in \mathbb{Z})\) 为前 \(k-1\) 个方程的通解。

那么目标变为求一个整数 \(t\),使得 \(x+t\times m\equiv a_k\pmod {m_k}\)

利用扩展欧几里得算法不难计算出 \(t\),倘若无解则直接输出 \(-1\)

求出之后令 \(x=x+t\times m\),继续求解即可。

所以最坏时间复杂度为 \(O(n\log m)\)其中 \(m={\rm lcm}(m_1,m_2,...,m_n)\)

放一下代码吧:

LL exgcd(LL a,LL b,LL &x,LL &y){
    if(!b){x=1,y=0;return a;}
    LL d=exgcd(b,a % b,x,y);
    LL z=x;x=y;y=z-y*(a/b);
    return d;
}

LL exCRT(){
    LL x,y;
    LL ans=s[1],m=mod[1];
    for(int i=2;i<=n;i++){
        LL a=m,b=mod[i];
        LL G=exgcd(a,b,x,y);
        LL c=(s[i] - ans%b + b) % b;
        if(c%G!=0) return -1;
        LL P=b/G;
        x=((x*c/G)%P+P)%P;
        ans+=x*m;
        m*=P;
        ans=(ans%m+m)%m;
    }
    return (ans%m+m)%m;
}

int main(){
    n=read();
    for(int i=1;i<=n;i++) mod[i]=read(),s[i]=read();
    printf("%lld\n",exCRT());
    return 0;
}

运用:求解线性同余方程组。

【卢卡斯定理】

卢卡斯定理:

对于质数 \(p\)

\[C_n^m=C_{n/p}^{m/p}\times C_{n\ {\rm mod}\ p}^{m\ {\rm mod}\ p} \pmod p \]

代码:

LL Lucas(LL n, LL m, LL p){
	if(n < m) return 0;
	if(n == 0) return 1;
	return Lucas(n/p, m/p, p) * C(n%p, m%p, p) % p;
}

应用:模数为质数且较小,但 \(n,m\) 较大时的组合数求解。

【正题】

终于到正题了

正题比前置芝士短系列

【例题一】

Devu and Flowers

给出多重集 \(S=\{a_1·s_1,a_2·s_2,\cdots,a_k·s_k\}\)(由 \(a_1\)\(s_1\)\(a_2\)\(s_2\cdots\) 组成)

\(n=\sum_{i=1}^k a_i\),求从这个集合中选出 \(r\) 个的组合数。

满足 \(0\leq r\leq n\),但不保证 \(r\leq a_i(1\leq i\leq k)\)

倘若题目保证 \(r\leq a_i(1\leq i\leq k)\)

那么有:

\[{\rm ans}=C_{r+k-1}^{k-1} \]

证明方法较多,我的理解是将问题转化为集合 \(S=\{k-1·1,r·0\}\) 的排列数。

如果不保证,我们可以利用容斥原理减去不合理的情况。

有:

\[{\rm ans}=C_{r+k-1}^{k-1}-\sum_{i=1}^{k} C_{r+k-1-(a_i+1)}^{k-1}+\sum_{1\leq i<j\leq k} C_{r+k-1-(a_i+1)-(a_j+1)}^{k-1}-\cdots + (-1)^k C_{r+k-1-\sum_{i=1}^{k}(a_i+1)}^{k-1} \]

这是简单的容斥原理。

众所周知,容斥原理的时间复杂度为 \(O(2^k)\),代码实现时直接枚举二进制子集即可。

同时利用费马小定理预处理逆元,从而 \(O(m)\) 求解 \(C_{n}^m\)

总时间复杂度:\(O(n2^n)\)

typedef long long LL;
const LL MOD = 1e9 + 7;

LL n, m;
LL a[30], inv[30];

LL Pow(LL a, LL b){
    LL sum = 1;
    for(; b; b >>= 1){
        if(b & 1) sum = sum * a % MOD;
        a = a * a % MOD;
    }
    return sum;
}

LL C(LL a, LL b){
    if(a<0 || b<0 || a<b) return 0;
    a %= MOD;
    if(a==0 || b==0) return 1;
    LL sum = 1;
    for(LL i=a; i>=a-b+1; i--) sum = sum * i % MOD;
    for(int i=1; i<=b; i++) sum = sum * inv[i] % MOD;
    return sum;
}

int main(){
    for(int i=1; i<=20; i++) 
        inv[i] = Pow(i, MOD - 2);
    scanf("%lld %lld", &n, &m);
    for(int i=1; i<=n; i++) scanf("%lld", &a[i]);
    LL ans = C(n+m-1, n-1);
    for(int i=1; i<(1 << n); i++){
        LL t = n+m-1;
        int p = 0;
        for(int j=0; j<n; j++)
            if(i >> j & 1){
                p++;
                t -= (a[j+1] + 1);
            }
        if(p & 1) ans = (ans - C(t, n-1)) % MOD;
        else ans = (ans + C(t, n-1)) % MOD;
    }
    printf("%lld\n", (ans + MOD) % MOD);
    return 0;
}

【例题二】

古代猪文

求:

\[g^{\sum_{k|n} C_{n}^{k}}{\rm mod}\ 999911659 \]

其中模数是一个质数,那么根据扩展欧拉定理

\[g^{\sum_{k|n} C_{n}^{k}}=g^{\sum_{k|n} C_{n}^{k}{\rm mod\ 999911658}}\pmod {999911659} \]

主要是求指数:\(\sum_{k|n} C_{n}^{k}{\rm mod\ 999911658}\)

可惜卢卡斯定理只能运用于模数较小且为质数的情况下。

但值得一提的是:\(999911658=2\times 3\times 4679\times 35617\)

对于这类质因子指数都为 \(1\) 的模数,求组合数时的一个常见的套路是:

若想要求 \(C_n^m\ {\rm mod}\ p\)\(a,p\) 又非常大,但 \(p\) 的每个质因子较小,且满足以上性质。

\(p=\Pi_{i=1}^n p_i\)

先对于每个质因子 \(p_i\),利用卢卡斯定理求出 \(a_i=C_n^m\ {\rm mod}\ p_i\)

最后解线性同余方程组:

\[\left\{\begin{aligned}x\equiv\ a_1\pmod {p_1} \quad\\ x\equiv\ a_2\pmod {p_2} \quad\\ x\equiv\ a_3\pmod {p_3} \quad\\ ...\quad\\x\equiv\ a_n\pmod {p_n} \quad\end{aligned}\right. \]

那么 \(x\equiv C_n^m\pmod p\)

至此问题完美解决。

本题十分综合,用到了:中国剩余定理扩展欧几里得算法乘法逆元中国剩余定理卢卡斯定理

果然毒瘤

typedef long long LL;

LL n, g, MOD = 999911659 - 1;
LL a[4], fac[40010];
LL P[4] = {2, 3, 4679 ,35617};

void Init(LL p){
	fac[0] = 1;
	for(int i=1; i<=P[3]; i++) fac[i] = fac[i-1] * i % p;
}

LL Pow(LL a, LL b, LL p){
	LL sum=1;
	for(; b; b >>= 1){
		if(b & 1) sum = sum * a % p;
		a = a * a % p;
	}
	return sum;
}

LL Exgcd(LL a, LL b, LL &x, LL &y){
	if(!b) {x=1, y=0; return a;}
	LL d = Exgcd(b, a % b, x, y);
	LL z = x; x = y; y = z - y * (a / b);
	return d;
}

LL Inv(LL a, LL p){
	LL x, y;
	Exgcd(a, p, x, y);
	return (x % p + p) % p;
}

LL C(LL n, LL m, LL p){
	if(n < m) return 0;
	return fac[n] * Inv(fac[m], p) % p * Inv(fac[n-m], p) % p;
}

LL Lucas(LL n, LL m, LL p){
	if(n < m) return 0;
	if(n == 0) return 1;
	return Lucas(n/p, m/p, p) * C(n%p, m%p, p) % p;
}

int main(){
	scanf("%lld %lld", &n, &g);
	if(g % (MOD + 1) == 0){
		puts("0");
		return 0;
	}
	for(int j = 0; j<4; j++){
		Init(P[j]);
		for(LL i=1; i*i<=n; i++)
			if(n % i == 0){
				a[j] = (a[j] + Lucas(n, i, P[j])) % P[j];
				if(i * i != n)
					a[j] = (a[j] + Lucas(n, n/i, P[j])) % P[j];
			}
	}
	LL ans=0;
	for(int i=0; i<4; i++){
		LL M = MOD / P[i];
		LL inv = Inv(M, P[i]);
		ans = (ans + inv * M % MOD * a[i] % MOD) % MOD;
	}
	MOD ++;
	printf("%lld\n", Pow(g, ans, MOD));
	return 0;
}

【总结】

简单数论小结。

引用资料&特别鸣谢:

  1. OI wiki数论部分。
  2. 《算法竞赛进阶指南》

完结撒花

posted @ 2021-03-10 15:33  LPF'sBlog  阅读(52)  评论(0编辑  收藏  举报