【算法笔记】组合数学-浅谈乘法逆元

目录

    • 前言
    • 说明
    • 栗子
    • 计算逆元
    • 解决方法
    • 例题1-小y的组合数取模问题
      • Description
      • Input
      • Output
      • Data
      • Solution
      • Code
    • 例题2-乘法序列
      • Description
      • Input
      • Output
      • Data
      • Solution
      • Code
    • 尾声

前言

O I OI OI 中,大多数情况下,善良的出题人为了避免高精度等大整数计算,常常会要求输出答案对一个数(大多是质数)取模的情况,但这衍生了一个问题:若题目中计算需用到除法而我们知道,如果 a ≡ b ( m o d c ) a \equiv b \pmod{c} ab(modc) 在大部分情况下 ⌊ a d ⌋ ≢ ⌊ b d ⌋ ( m o d c ) \lfloor \frac {a} {d} \rfloor \not\equiv \lfloor \frac {b} {d} \rfloor \pmod{c} dadb(modc) (注意一般题目会默认对一个数取模是数论(整数)意义上的取模,故这里除法为整数除法),这和等式的性质是不同的,要解决这个问题,就需要用到一个概念:乘法逆元。

说明

我们来举个例子吧,先再实数范围举例,由小学知识可知,如果一个代数式 F F F 乘一个数 a a a 后,再乘它的倒数 1 a \frac {1} {a} a1 ,相当于没有乘 a a a (这里不考虑 0 0 0 的情况),换句话说,我们乘 1 a \frac {1} {a} a1 后,取消了代数式 F F F a a a 后值增大的影响。
不难发现这符合逆元的定义,故我们可以说一个数和其倒数互为乘法逆元。除此之外,我们还能发现一个数和其相反数互为加法逆元等等……

接下来回到代数式的例子,考虑为什么 a a a 的倒数 1 a \frac 1 a a1 能消去乘 a a a 的影响。显然,是由于乘法结合律的存在,使得我们在运算 F × a × 1 a F \times a \times \frac 1 a F×a×a1 时可以先运算 a × 1 a a \times \frac 1 a a×a1 的值,再运算它和 F F F 的乘积,而 a × 1 a = 1 a \times \frac 1 a = 1 a×a1=1 ,任何数与 1 1 1 的乘积均为其本身,从而使乘 a a a F F F 值增大的影响取消。

上文在实数范围内讨论了乘法逆元,现在我们来看本文主要讨论的内容,数论模意义下的乘法逆元。

由上文的分析来看,我们可以借助“任何数与 1 1 1 的乘积均为其本身”的性质定义模意义下的乘法逆元。

故其定义为:如果说 a a a 在模 p p p 意义下的乘法逆元是 x x x ,那么 a x ≡ 1 ( m o d p ) ax \equiv 1 \pmod{p} ax1(modp)

栗子

举个栗子:

15 3 % 7 \frac {15} 3 \% 7 315%7

在 mod 7 意义下 3的逆元是5

所以 在 mod 7意义下 ( 15 / 3 ) % 7 = ( 15 ∗ 5 ) % 7 (15/3) \% 7 = (15 *5) \%7 (15/3)%7=(155)%7

计算逆元

  • 然而怎么快速计算逆元呢?
  • 费马小定理:当 p p p 为素数时, a p − 1 ≡ 1 a^{p-1}\equiv1 ap11 ( m o d p ) \pmod p (modp)
    那么 a ∗ a p − 2 ≡ 1 a*a^{p-2}\equiv1 aap21 ( m o d p ) \pmod p (modp)
    再看看,这是不是又是逆元的定义?快速幂求出 a p − 2 a^{p-2} ap2 即可。

解决方法

预处理阶乘和阶乘逆元法 \color{red} \text{预处理阶乘和阶乘逆元法} 预处理阶乘和阶乘逆元法

根据计算式得到
( m n ) = f a c m × i n v f n × i n v f m − n \binom{m}{n}=fac_m\times invf_n \times invf_{m-n} (nm)=facm×invfn×invfmn
我们事先预处理阶乘及阶乘逆元,关于预处理阶乘逆元,有

i n v f i ≡ i n v f i + 1 × ( i + 1 ) ( m o d p ) invf_{i} \equiv invf_{i+1}\times (i+1) \pmod p invfiinvfi+1×(i+1)(modp)

证明:

f a c i × i n v f i ≡ 1 ( m o d p ) fac_i \times invf_i \equiv 1 \pmod p faci×invfi1(modp)

f a c i − 1 × i × i n v f i ≡ 1 ( m o d p ) fac_{i-1}\times i \times invf_i \equiv 1 \pmod p faci1×i×invfi1(modp)

只需要算一个就可以了

复杂度 O ( n + log ⁡ p ) ∼ O ( 1 ) O(n+\log p)\sim O(1) O(n+logp)O(1) p p p 为质数

例题1-小y的组合数取模问题

Description

共有 T T T 组询问,每组询问给出一对 n , m n,m n,m,求 C n m C_n^m Cnm,为了不让大家用到高精度,答案对 100000007 100000007 100000007 取模。

Input

第一行一个数 T T T,接下来 T T T 行每行两个数 n , m n,m n,m,意义如题面所述。

Output

T T T 行每行一个数表示答案。

Data

对于 10 10% 10 的数据, 1 ≤ n , m ≤ 51 ≤ n , m ≤ 5 1≤n,m≤51≤n,m≤5 1n,m51n,m5

对于 30 30% 30 的数据, 1 ≤ n , m ≤ 1001 ≤ n , m ≤ 100 1≤n,m≤1001≤n,m≤100 1n,m1001n,m100

对于 60 60% 60 的数据, 1 ≤ n , m ≤ 1000 1≤n,m≤1000 1n,m1000

对于 100 100% 100 的数据, 1 ≤ x < y ≤ 2 n 1≤x1x<y2n 1 ≤ n , m ≤ 105 1≤n,m≤105 1n,m105

Solution

  • 首先这道题显然 要用逆元?【大雾】
  • 就是一个逆元的板子?

Code

#include 
using namespace std;
typedef long long LL;
const LL p=100000007,N=1000010,maxn=1000010;
LL fac[maxn],Inv[maxn];
LL Mul(LL x,LL y) { return 1LL*x*y%p; }
LL Fast_Exponentiation(int x,int y)
{
	int ans=1;
	for (;y;y>>=1,x=Mul(x,x))
	 if (y&1) ans=Mul(ans,x);
	return ans;
}
void before()
{
	fac[0]=Inv[0]=1;
	for (int i=1;i<=N;i++) fac[i]=Mul(fac[i-1],i);  //计算阶乘
	Inv[N]=Fast_Exponentiation(fac[N],p-2);  //根据费马小定理,N的阶乘的p-2次方就是它的逆元
	for (int i=N-1;i>=1;i--) Inv[i]=Mul(Inv[i+1],i+1);  //根据上文的公式,公式中的i-1现在是i,那么公式中的i现在就是i+1了
}
void work()
{
	int T;
	scanf("%d",&T);
	for (int i=1;i<=T;i++)
	{
		LL n,m;
		scanf("%lld %lld",&n,&m);
		if (n<m) printf("0\n");
		else printf("%lld\n",Mul(fac[n],Mul(Inv[m],Inv[n-m])));  //得出m的阶乘的逆元和(n-m)的阶乘的逆元,x/m!/(n-m)!=n*Inv[m]*Inv[n-m]
	}
}
void init()
{
	freopen("math.in","r",stdin);
	freopen("math.out","w",stdout);
}
int main()
{
	init();
	before();
	work();
	return 0;
}

例题2-乘法序列

Description

给定整数 N N N M M M,请找出有多少个不同的序列 a 1 × a 2 × ⋯ × a N = M a_1×a_2×⋯×a_N=M a1×a2××aN=M
你只需要输出答案 对 1 0 9 + 7 10^9+7 109+7 取余即可。

两个不同的序列指的是按顺序只要有一个元素不同即可。具体看样例解释。

Input

两个整数 N N N M M M

Output

一个整数表示答案,注意取余

Data

1 ≤ N ≤ 1 0 5 , 1 ≤ M ≤ 1 0 9 1≤N≤10^5,1≤M≤10^9 1N105,1M109

Solution

  • 这道题需要一定的思维转化,首先是分解质因数,要记录每个质因子的个数 c [ i ] c[i] c[i]
  • 对于每个质因子都有 c [ i ] c[i] c[i]个,我们要把 c [ i ] c[i] c[i] 分成 n n n 组,相当于分到 n n n 个盒子里,因为根据题意,盒子可以为空,所以答案为 C n + c [ i ] − 1 n − 1 C_{n+c[i]-1}^{n-1} Cn+c[i]1n1
  • 而且题目是分步做,所以开一个 f o r for for 循环 a n s ans ans 相乘就可以了

Code

#include 
using namespace std;
typedef long long LL;
const LL Mod=1e9+7,N=200000,maxn=200010;
LL ans=1,t=0;
LL fac[maxn],Inv[maxn],p[maxn],c[maxn];
LL Mul(LL x,LL y) { return 1LL*x*y%Mod; }
LL C(LL n,LL m)
{
	return Mul(fac[n],Mul(Inv[m],Inv[n-m]));
}
LL Fast_Exponentiation(int x,int y)
{
	LL ans=1;
	for (;y;y>>=1,x=Mul(x,x))
	 if (y&1) ans=Mul(ans,x);
	return ans;
}
void before()
{
	fac[0]=Inv[0]=1;
	for (int i=1;i<=N;i++) fac[i]=Mul(fac[i-1],i);
	Inv[N]=Fast_Exponentiation(fac[N],Mod-2);
	for (int i=N-1;i>=1;i--) Inv[i]=Mul(Inv[i+1],i+1);
}
void divide(int n)
{
	for (int i=2;i<=sqrt(n)+1;i++)
	{
		if (n%i==0)
	 	{
	 		p[++t]=i,c[t]=0;
	 		while ( n%i == 0) n= n/ i,c[t]++;
		}
	}
	if (n>1)
	{
		p[++t]=n,c[t]=1;
	}
}
int main()
{
	freopen("factor.in","r",stdin);
	freopen("factor.out","w",stdout);
	int n,m;
	scanf("%d %d",&n,&m);
	
	divide(m);  //分解质因数
	
	before();  //预处理

	for (int i=1;i<=t;i++)
	{
		ans=Mul(ans,C(n+c[i]-1,n-1));  //上文已经提到了
	}
	printf("%lld",ans);
	return 0;
}

尾声

这篇文章还是极短的,很多引用了老师的课件? 仅以此写一些自己的心得与体会,此后还会有更新,但前提是蒟蒻得学更多新的知识QAQ

你可能感兴趣的:(算法,数据结构笔记/学习总结)