快速幂+龟速乘+费马小定理+逆元+矩阵乘法
我是这个机房最菜的
我今天复习的是:
王者吃鸡CF,上分小队等你来
扯远了,接下来才是干货
快速幂+慢速乘+费马小定理+逆元+矩阵乘法(讲错了还请笑的收敛点
本来太蒻,所以快速幂,慢速乘,费马小定理没有找到合适的例题,逆元和矩阵乘法的例题也不多而且不难
快速幂
说到求几次方,我们不难想到\(pow()\)这个函数,但是这个函数不仅不快而且还有坑(某位大佬曾因这个在省选翻车了),用\(pow()\)切记要注意\(pow()\)返回\(double\)类型的数
于是我们便要xiao习一下更快而且没那么容易锅的\(ksm\)这不有手就行嘛
基本思路:
1)当\(b\)是奇数时,那么有 \(a^b = a * a^{b-1}\)
2)当\(b\)是偶数时,那么有 \(a^b = a^{b/2} * a^{b/2}\)
时间复杂度: \(O(log\ n)\)
代码实现:
//递归写法
int ksm(int x, int y, int mod){//求x的y次方(在%m的意义下)
if(y == 0) return 1;
if(y % 2 == 0){
int num = ksm(x ,y / 2) % mod;//防止计算两次,节省时间
return num * num % mod;
}
else return ksm(x, y - 1) * x % mod;
}
//while写法
int ksm(int x, int y){
int res = 1;
while(y){
if(y & 1) res =(res * x) % mod ;
x = x * x % mod;
y >>= 1;
}
return res % mod;
}
优化:
if(y%2==1)
等效于
if(y&1)
因为y&1是按位与,判断y的末尾是否为1因此当y为奇数时y&1返回为1,if条件成立,这样执行速度更快
注意:
针对不同的题目,有两个细节需要注意
1)如果初始值x 大于 m ,那么需要在进入函数前就让x对m取模,
2)若果m 为 1,可以直接在函数外部特判为 0,不需要进入函数来计算。(因为任何数对1 取模都是0。
例题水题:
这道题相信许多人都已经AC
这道题的核心柿子是$$m{n}-m*(m-1)$$
至于为什么呢?这似乎不属于我讲的范围
但我们考虑数据范围
对于100%的数据,保证\(1<=m<=1e8, 1<=n<=1e12\)
这个数据求个\(m^{n}\)和\(m*(m-1)^{n-1}\)就很容易炸了吧
然后我们就可以想到\(ksm\)
没错,就是这东西
long long ksm(long long x, long long y){
long long res = 1;
while(y){
if(y & 1) res =(res * x) % mod ;
x = x * x % mod;
y >>= 1;
}
return res % mod;
}
慢速乘(俄罗斯农民乘法)
由来:
计算机很笨当然不是了,乘法很慢,还动不动就溢出,于是慢速乘出现了。
在很久很久以前。。。人们是这样做乘法的。。。这是一种古老的乘法算法,但是在如今的计算机中却还能发现它的身影。它,就是以俄罗斯农民命名的乘法算法!
我们的慢速乘(龟速乘)主要是为了防止乘法溢出的
原理听我口胡:
假设我们现在有两个数\(x\)和\(y\),我们要得到\(x*y\),如果给你的数据是
$ 1 <= x,y <= 1000000 $
这完美的炸掉了\(int\)
肯定有聪明的童鞋说道:你开个 $ long\ long$不就完了吗?
就这?就这,我果然很菜
如果再大一点点的话肯定有孩纸说\(unsigned\ long\ long\)就好了
这不有手就行嘛
我先扶一下自己的假肢(手动滑稽
那如果这样呢
\(1 <= x,y <= 1000000000000000\)
这种毒瘤咋办呢???
在这就发挥出了慢速乘的作用了
那慢速乘是怎么实现的呢?
我们可以把\(x,y\)看成二进制下的乘
我们把\(y\)拆成好多个2来解决,边乘边模
是不是顿悟了许多呢!!!
代码实现:
long long mul(long long a, long long b){
long long res = 0;
while(b){
if(b & 1) res += a;
a <<= 1;
b >>= 1;
}
return res;
}
切记切记切记!!!
这里是 a <<= 1;
而不是 a *= a;
emmmm至于例题嘛
本人太菜了,没找到合适的例题(大家都会敲慢速乘就好了
费马小定理
没错,费马小定理就是这个人提出来的虽然百度百科给ta的职业定位是律师和业余数学家
虽然费马小定理还有个哥哥:费马大定理(又名费马最后的定理)
他断言当整数n >2时,关于x, y, z的方程 $$x^n + y^n = z^n$$ 没有正整数解。
当然我们也不需要记住它,因为我们要学的是费马小定理
主要内容就是:
如果p是一个质数,而整数a不是p的倍数,则有$$a^{p-1}≡1\ (mod\ p)$$
满足费马小定理的条件就是: \(p\)为质数, 且\(a\)与\(p\)互质
你们想看证明吗我知道其实你们不想
但还是得讲呀~~其实跳过去也阔以
证明:
声明:
- 我不是用欧拉函数证明的,我怕讲了别人准备的欧拉函数,所以用另一种简单的方式去证明
(如果你们想看欧拉函数证明的戳这里
emmmm....有点突发状况 ,欧拉定理我也讲,费马小定理的欧拉定理的证明在补充的位置- 在证明之前我们先给出两个引理(
请抓好扶好,前方有些曲折- 还有个很重要的事情就是:切记!!!取模运算对于加法、减法、乘法都封闭(可以参考最后的补充部分)
<一>
引理<一>:
假设\(p\)与\(c\)互质,且存在\(a*c\equiv b*c\ (mod\ p)\)
则$a\equiv b\ (mod\ p) $
注:引理<一>和取模运算的第二条定理的区别在于正反推,而且取模运算第二条定理对于\(c\)没有做要求,而这个引理<一>要保证\(p\)与\(c\)互质
证:
∵\(a*c\equiv b*c\ (mod\ p)\)
∴\((a*c - b*c)\equiv 0\ (mod\ p)\)
∴\((a-b)*c\equiv 0\ (mod\ p)\)
∴\((a-b)*c = k*p\ (k\in Z)\)
∵\(p\)与\(c\)互质
∴\(k'*c*p\equiv 0\ (mod\ p)\)
又因为\((a-b)*c = k*p\ (k\in Z)\)
即:\((a-b)*c \equiv 0\ (mod\ p)\)
∴\(k'*p\equiv (a-b)\ (mod\ p)\)
即\((a-b)\equiv 0\ (mod\ p)\)
∴\(a\equiv b\ (mod\ p)\)
证毕。
<二>
首先引入一个概念叫作剩余系。。
剩余类,亦称同余类,是一种数学的用语,为数论的基本概念之一。设模为n,则根据余数可将所有的整数分为n类,把所有与整数a模n同余的整数构成的集合叫做模n的一个剩余类,记作[a]。并把a叫作剩余类[a]的一个代表元。
然后我们在根据这个引入另一个概念叫作完全剩余系。。。
从模n的每个剩余类中各取一个数,得到一个由n个数组成的集合,叫做模n的一个完全剩余系。完全剩余系常用于数论中存在性证明
如果没有透彻意思的可以举个栗子:就是说在%n的意义下,\(0, 1, 2,3,\cdots,n-1\)就是模n的一个完全剩余系
引理<二>:
若\(a_{1},a_{2},a_{3},\cdots,a_{p}\) 为\(mod\ p\) 的完全剩余系,\(p\)与\(b\)互质,
则\(a_{1}*b, a_{2}*b, a_{3}*b,\cdots,a_{p}*b\)也是\(mod\ p\) 的完全剩余系
证:
利用反证法:
假设存在\(b*a_{i}\equiv b*a_{j}\ (mod\ p)\),根据引理<一>可以得到\(a_{i}\equiv a_{j}\ (mod\ p)\)
这显然不成立,
∴引理<二>成立
证毕。
我们便可以开始证明费马小定理了
证:
\(0,1,2,\cdots, p-1\)是%\(p\)的完全剩余系,这很显然吧
∵\(a\)与\(p\)互质
∴根据引理<二>可以得知:\(0,a*1,a*2,\cdots, a*(p-1)\)也是%\(p\)的完全剩余系
∵\(\left\{ \begin{array}{lrc} 1\equiv a*k_{1}\ (mod\ p)\\ \vdots \\p-1\equiv a*k_{p-1}\ (mod\ p)\end{array} \right.\)\(\left\{ \begin{array}{lrc} k_{1}\in\ (1,\cdots,p-1)\\ \vdots\ \\k_{p-1}\in\ (1,\cdots,p-1)\end{array} \right.\)
∴根据取模运算对乘法是封闭的得出:\(1*2*\cdots*(p-1)\equiv a*1*a*2*\cdots*a*(p-1)\ (mod\ p)\)
即:\((p-1)!\equiv (p-1)!*a^{p-1}\ (mod\ p)\)
根据引理<一>两边同时约去\((p-1)!\)
就可以得到\(1\equiv a^{p-1}\ (mod\ p)\)
即费马小定理:\(a^{p-1}\equiv 1\ (mod\ p)\)
证毕。
是不是透彻了许多呢???
应用:
求逆元,没错,就是我们下面要讲的逆元
例题:
这道题我们用费马小定理做的话,会T掉三个点,所以我们先不讲这道题,我们先讲一下逆元
逆元
定义:
逆元素是指一个可以取消另一给定元素运算的元素。 ————某度某科
这个定义似乎不太那么直观……
我们来举个例子吧,先再实数范围举例,由小学知识可知,如果一个代数式 \(F\) 乘一个数 \(a\) 后,再乘它的倒数 \(\frac 1{a}\) ,相当于没有乘 \(a\)(这里不考虑 \(0\) 的情况),换句话说,我们乘 \(\frac 1{a}\) 后,取消了代数式 \(F\) 乘 \(a\) 后值增大的影响。
我们换句话说就是:
如果说 a 在模 p 意义下的乘法逆元是 x,那么 $ a*x≡1\ (mod\ p)$
我们通常把\(a\)的逆元记为\(a^{-1}\)
就像\(0\)没有倒数一样,\(0\)也没有逆元
当且仅当\(gcd(a,p) =1\)时,\(a\)的逆元有唯一解,否则无逆元
不难发现,\(a\)在模 \(p\) 意义下的乘法逆元为 [1,p - 1] 中的整数。
(这不难理解吧,因为如果\(a^{-1} >= p\)的话,在模\(p\)的意义下就会抵消
求法:
今天我要讲五种求逆元的算法(认真听,万一我没睡醒,把你带进无尽深渊
如果没听懂的话我甩了个链接
(1)拓展欧几里得求逆元
拓展欧几里得相信大家都会吧!!!
∵\(a*a^{-1}\equiv 1\ (mod\ p)\)
∴我们设\(x=a^{-1}\),我们就可以得到
我们把这个柿子巧妙地转化一下就变成了:\(a*x-k*p= 1\)
我们令\(y=-k\),则\(a*x+p*y=1\)
我们发现这个方程有解的条件是 \(gcd(a,p)=1\),即 \(a,p\) 互质,所以得出结论: *\(a\)在模\(p\) 意义下的乘法逆元存在当且仅当 \(a,p\)互质,这也在一定程度上解释了大多数题目模数为什么为质数的原因:(设模数为 \(p\))可以保证在 [1,p−1] 中的所有整数都有模 \(p\) 意义下的逆元。
∵\(gcd(a,p)=1\)
∴我们可以用拓展欧几里得求方程\(a*x+p*y = gcd(a,p)\),即\(a*x+p*y=1\)
最后求出\(x\)的值,即为\(a\)的逆元
时间复杂度为\(O(log\ p)\)
参考代码(本人码风较丑,勿喷
//luogu P3811 会T掉最后一个点(貌似开long long的话T俩点)
#include<bits/stdc++.h>
using namespace std;
int n, p, x, y, a[30000005];
void exgcd(int a, int b){
if(b == 0){
x = 1, y = 0;
return ;
}
exgcd(b, a % b);
int t = x;
x = y;
y = t - a / b * y;
}
int main(){
scanf("%d %d", &n, &p);
for(int i = 1; i <= n; i ++){
exgcd(i, p);
a[i] = (x + p) % p;
}
for(int i = 1; i <= n; i ++){
printf("%d\n", a[i]);
}
return 0;
}
(2)费马小定理求逆元
前面我们刚复习了一下费马小定理(你要是现在说不会或者说忘了,我就要抽出我四十米长的大刀来给你削个苹果,因为你太秀了)
上面我手打了那么多行的证明,大家应该都透彻费马小定理的正确性了吧
我们想一下
\(\left\{ \begin{array}{lrc}a*a^{-1}\equiv 1\ (mod\ p)\\ a^{p-1} \equiv 1\ (mod\ p)\end{array} \right.\)
我们把\(a^{p-1}\)转化为\(a*a^{p-2}\)
我们会惊奇地发现\(a^{p-2}=a^{-1}\)
于是我们找到了费马小定理求逆元的方法
时间复杂度为\(O(log\ p )\)
参考代码
//luogu P3811 会T掉三个点
#include<bits/stdc++.h>
using namespace std;
long long n, mod;
long long ksm(long long x, long long y){
if(!y) return 1;
if(y & 1) return (ksm(x, y - 1) * x) % mod;
long long res = ksm(x, y >> 1) % mod;
return res * res % mod;
}
int main(){
scanf("%lld %lld", &n, &mod);
for(int i = 1; i <= n; i ++){
printf("%lld\n", (ksm(i, mod - 2) % mod + mod) % mod);
}
return 0;
}
细心的小盆友会发现这两种方法的时间复杂度不优
(3)线性求逆
注:这种方法仅适用于当\(p\)为质数的时候
1在模任何正整数意义下的乘法逆元都是1,即:\(1^{-1}=1\ (mod\ n)\ (n\in Z)\)
我们要求在模\(p\)意义下\(1\)~\(n(1<=n<p)\)的逆元,我们设\(p=k*i + m\)
我们不难想到\(k = \lfloor \frac {p}{i} \rfloor ,\ m = p\ mod\ i\)
∴我们可以列出方程\(k*i+m\equiv 0\ (mod\ p)\)
然后两边同乘\(i^{-1},\ m^{-1}\)
我们得到\(k*m^{-1}+i^{-1}\equiv 0\ (mod\ p)\)
∴\(i^{-1}\equiv -k*m^{-1}\ (mod\ p)\)
又∵$k = \lfloor \frac {p}{i} \rfloor ,\ m = p\ mod\ i $
∴\(i^{-1}\equiv -\lfloor \frac {p}{i} \rfloor *(p\ mod\ i)^{-1}\ (mod\ p)\)
但是我们考虑一点,\(-\lfloor \frac {p}{i} \rfloor\)是个负数,我们在模\(p\)的意义下,效果是等效于\((p - \lfloor \frac {p}{i} \rfloor)\)
∴我们的柿子又阔以变成了这个鬼亚子\(i^{-1}\equiv (p- \lfloor \frac {p}{i} \rfloor)*(p\ mod\ i)^{-1}\ (mod\ p)\)
这不就得到了一个递推式了嘛
时间复杂度\(O(n)\)
参考代码如下:
//luogu P3811 这样就可以很愉悦的AC了
#include<bits/stdc++.h>
using namespace std;
const int N = 3e6 + 5;
long long n, p, inv[N];
int main(){
scanf("%d %d", &n, &p);
inv[1] = 1;
for(int i = 2; i <= n; i ++){
inv[i] = (p - p / i) * inv[p % i] % p;
}
for(int i = 1; i <= n; i ++){
printf("%d\n", inv[i]);
}
return 0;
}
注:
(1):别忘了给inv[1]赋初值
(2):别忘了开long long(不开long long见祖宗
(4)欧拉定理求逆元
首先我们要先了解一下欧拉定理
若\(p,a\)为正整数,且\(p,a\)互质,则:\(a^{\varphi(p)}\equiv 1\ (mod\ p)\)
欧拉定理不归我讲,所以记住就好了
(如果真的想看证明的话戳这里,和上面甩的那个费马小定理的证明是同一篇文章
emmmm......事情发生了点转变,欧拉定理还是我讲吧(我把证明就放在最后的补充里面吧)
∵\(a^{\varphi(p)}\equiv 1\ (mod\ p)\)
∴\(a*a^{\varphi(p)-1}\equiv 1\ (mod\ p)\)
又∵\(a*a^{-1}\equiv 1\ (mod\ p)\)
∴\(a^{-1}\equiv a^{\varphi(p)-1}\ (mod\ p)\)
引理:对于正整数\(n\)进行唯一分解,得到柿子\(\displaystyle n=p_{1}^{k_{1}}\times p_{2}^{k_{2}}\times \cdots \times p_{m}^{k_{m}}=\prod_{i=1}^{m}p_{i}^{k_{i}}\)
则:\(\displaystyle \varphi(n)=n\times(1-\frac 1{p_{1}})\times(1-\frac 1{p_{2}})\times\cdots\times(1-\frac 1{p_{m}})=n\times \prod_{i=1}^{m}(1-\frac 1 {p_{i}})\)
然后我们转换一下形态:\(\displaystyle \varphi(n) = n\times \prod_{i = 1}^{m}(\frac {p_{i}-1}{p_{i}})\)
我们可以在\(O(\sqrt {n})\)的时间内枚举每个质因子,因为每个质因子的贡献只有一次,所以用完之后要记得消去,
这里有一个很巧妙的防止炸的方法就是首先令最初的\(ans = n\),然后每次累乘\(\frac {p_{i}-1}{p_{i}}\)就好了,我们假设当前进行到了第\(m\)次累乘,所以当前\(\displaystyle ans=n*\prod _{i=1}^{m-1}{\frac{p_{i}-1}{p_{i}}}\),于是我们要得到的新的\(ans\),便可以用\(ans=(\frac {ans}{p_{m}})*( p_{m}-1)\),最后我们用快速幂求一下\(a^{\varphi(p)-1}\)就欧克了
时间复杂度:\(O(log\ \varphi(p)+\sqrt{p})\)
参考代码:
#include<bits/stdc++.h>
using namespace std;
int n, p;
int phi(int x){
int res = x, tmp = x;
for(int i = 2; i * i <= tmp; i ++){
if(x % i == 0){
res = ((res / i) % p * (i - 1) % p) % p;
while(x % i == 0) x /= i;
}
}
if(x > 1) res = ((res / x) % p * (x - 1) % p) %p;
return res % p;
}
int ksm(int x, int y){
if(!y) return 1;
if(y & 1) return x * ksm(x, y - 1) % p;
int res = ksm(x, y >> 1) % p;
return res * res % p;
}
int main(){
scanf("%d %d", &n, &p);
int tmp = phi(p) - 1;
printf("%d\n", ksm(n, tmp));
return 0;
}
(5)离线逆元
看我刚才甩的博客上说这个算法貌似很难(其实不是很难,认真听绝对能听懂,相信我
这个算法主要的用处在于,给定\(n\)个整数,不一定相邻,就是随机的那种,然后让你求一下每个\(a_{i}\)的逆元
我们看到这个的时候,在没学这个算法之前可能会用线性求逆来求,也有可能用费马小定理或拓展欧几里得求,但是,今天学了这个算法之后,你就会爱上这个神奇的算法
我们先看一道题
看到这道题,我们首先考虑线性求逆的复杂度\(O(max(a_{i}))\),在这道题中显然不是很优,因为时间空间都会被卡(而且出题人也不傻,怎么可能乘法逆元一的正解就是乘法逆元二的呢)
我们再考虑一下费马小定理和拓展欧几里得的\(O(n\ (log\ p))\),貌似更炸.
然后我们讲完离线逆元之后我们再考虑一下这个神奇的算法的可行性
首先我们要知道一个事情就是:前缀积的逆元就是逆元的前缀积(即逆元是完全积性的)
证:我们设任意两个整数\(a\)和\(b\),我们看一下他们在模\(p\)意义下一定满足\(a^{-1}*b^{-1}\equiv (a*b)^{-1}\ (mod\ p)\)
我们根据逆元的定义可以得到柿子\(a*a^{-1}*b*b^{-1}\equiv 1\ (mod\ p)\)
然后我们把\(a*b\)看做一个整体,然后这个整体的逆元就是\((a*b)^{-1}\)
我们可以知道\((a*b)*(a*b)^{-1}\equiv 1\ (mod\ p)\)
∴\((a^{-1}*b^{-1})\equiv (a*b)^{-1}\ (mod\ p)\)
根据这个性质,我们可以用一个\(pre_{n}\)数组来存储\(a_{n}\)的前缀积(不是前缀和!!!)
∵\(\displaystyle pre_{n}=\prod_{i=1}^{n}a_{i}\)
∴\(\displaystyle pre_{n}^{-1}=\prod_{i=1}^{n}a_{i}^{-1}\)
然后我们就会发现:
\(a_{i}^{-1}=pre_{i}^{-1}*pre_{i-1}\ (1<=i<=n)\)
\(pre_{i}^{-1}=pre_{i+1}^{-1}*a_{i+1}\ (1<=i<n)\)
然后我们是不是阔以根据这两个柿子来做题了呢
我们考虑一下他的时间复杂度:线性的枚举\(O(n)\)+费马小定理(或拓展欧几里得)求\(pre_{n}^{-1}\)的\(O(log\ p)\)
时间复杂度为:\(O(n+log\ p)\)
但是这道题还会卡常,所以我们还要用快读
因为这道题最后要求的是\(\displaystyle\sum_{i=1}^{n}{\frac {k^{i}}{a_{i}}}\),所以我们可以转换为\(\displaystyle \sum_{i=1}^{n}{k^{i}*a_{i}^{-1}}\)
我太菜了,这道题我交了20次才A,还是在\(hjr\)童鞋的帮助下
参考代码:
#include<cstdio>
using namespace std;
const int N = 5e6 + 5;
int n, p, k, pre[N], inv_pre[N], a[N];
inline int read(){
int op = 0, opp = 1; char ch = getchar();
while(ch > '9' || ch < '0'){ if(ch == '-') opp = -1; ch = getchar();}
while(ch >= '0' && ch <= '9'){ op = (op << 1) + (op << 3) + (ch - '0'); ch = getchar();}
return 1ll * op * opp;
}
inline int ksm(int x, int y, int p){
int res = 1;
while(y){
if(y & 1) res = 1ll * res * x % p;
x = 1ll * x * x % p;
y >>= 1;
}
return res;
}
int main(){
n = read(), p = read(), k = read();
pre[0] = 1;
for(register int i = 1; i <= n; ++ i){
a[i] = read();
pre[i] = 1ll * pre[i - 1] * a[i] % p;
}
inv_pre[n] = 1ll * ksm(pre[n], p - 2, p) ;
for(register int i = n; i >= 1; -- i){
inv_pre[i - 1] = 1ll * inv_pre[i] * a[i] % p;
}
int ans = 0;
for(register int i = n ; i ;i --){
ans = (ans + 1ll * inv_pre[i] * pre[i - 1] % p) * k % p;//直接在计算中求的逆元,并且逆序不用担心k(这很细节操作,我一直被卡在了这)
}
printf("%d\n", ans);
return 0;
}
关于这道题卡常,我在luogu看到了一篇帖子
- 换成快读,用fread而不是getchar
- i++换成++i,i--同理
- 尽可能减少%p
- 快读中的*10,替换成(sum<<1)+(sum<<3)
- 减少类型转换
- 循环变量等任何局部变量,定义前加入register关键字(例如
for (register int i=1;i<=n;++i;)
)- 减少调用头文件
- 吸臭氧(厌氧型代码可能负优化)
- 对于我个人的代码,其中register和fread是最高效率的。
还有一个我补充的(因为我那20次提交中,有一次用\(inline\),优化成功了一个点):就情况写\(inline\)
对于这些优化常数的东西,没有试过,
因为我太菜了, 不会常数优化,所以我又来甩链接了,相同的链接,不同的视觉效果
这篇博客较为系统全面地介绍了优化常数的东西
- 声明:对于乘法逆元二这道题,我看第一篇题解贼简单,思想其实和这个离线逆元有那么一丢丢的相似
啊这!! !你们想听吗?反正我在最后的补充那写了(你们也可以自己去康康)
在弄完这个方法之后,我这道题前前后后一共交了40遍,多多少少有点想吐,咳!!!
阶乘逆元:
没错,你没有看错,就是阶乘逆元
其实思想特别~简单
我们考虑要计算\({(n!)}^{-1}\)的话,如果对每个元素都单独求一遍的话,是会炸的!!!
然后我们考虑上面讲到的第五种求逆元的方法———离线逆元,
我们会发现,我们可以把\((n!)\)看成当时的\(pre_{n}\)数组,然后根据递推式求就好了
怎么样,是不是很简单呀!!!
参考代码:
#include<cstdio>
#include<iostream>
using namespace std;
const int N = 5e6 + 5;
int n, p, pre[N], inv_pre[N], inv[N];
inline int read(){
int op = 0, opp = 1; char ch = getchar();
while(ch > '9' || ch < '0'){ if(ch == '-') opp = -1; ch = getchar();}
while(ch >= '0' && ch <= '9'){ op = (op << 1) + (op << 3) + (ch - '0'); ch = getchar();}
return op * opp;
}
inline int ksm(int x, int y, int p){
int res = 1;
while(y){
if(y & 1) res = res * x % p;
x = x * x % p;
y >>= 1;
}
return res % p;
}
int main(){
n = read(), p = read();
pre[0] = 1;
for(register int i = 1; i <= n; ++ i){
pre[i] = pre[i - 1] * i % p;
}
inv_pre[n] = ksm(pre[n], p - 2, p) ;
for(register int i = n; i >= 1; -- i){
inv[i] = inv_pre[i] * pre[i - 1] % p;
inv_pre[i - 1] = inv_pre[i] * i % p;
}
for(register int i = 1; i <= n; i ++){
printf("%d\n", (inv[i] % p));
}
return 0;
}
逆元的知识到此结束了,前方到站———矩阵乘法
逆元还有没有什么不透彻的地方
矩阵乘法
还是老规矩,先甩一篇博客,如果我讲的实在听不懂, 可以看看那篇博客
事先说好,我矩阵乘法不好我似乎啥也不好emmm...容易把人带到无尽深渊,还有就是本人太菜了,不太会用\(markdown\)这高级玩意,所以矩阵有点丑的话还请见谅吧
定义:
首先我们要先了解一下矩阵这东西
来自某度某科的一段话,
然后,我们现在可以知道什么是矩阵了吧
矩阵乘法运算法则及性质:
我们考虑什么是矩阵乘法呢???
矩阵乘法(蒟阵乘法),顾名思义,就是把两个矩阵(蒟蒻)乘起来,但是和普通乘法不太一样
如果还是不能理解矩阵乘法的话,刘汝佳写的那本蓝皮书的\(P151\)页有和线性方程联系起来的解释
两个矩阵能相乘的条件:
这个很重要,赶紧拿小本本记下来
当一个矩阵的行数等于另一个矩阵的列数的时候,相乘才有意义
而且假设一个\(p*n\)的矩阵和一个\(n*k\)的矩阵相乘,我们最后得到的新矩阵是\(p*k\)的,\(p,k\)不一定相等
运算法则:
首先我们需要三个矩阵(蒟蒻)\(A,B,C\)
我们假设矩阵\(C=A*B\)
我们假设\(A\)矩阵是\(n*m\)的,\(B\)矩阵是\(m*p\)的,然后我们可以知道\(C\)矩阵是\(n*p\)的,且\(\forall i \in [1,n], \forall j \in [1,p]\)
然后我们发现\(\displaystyle C_{i,j} = \sum _{k=1}^{m}A_{i,k}*B_{k,j}\)
然后我们用矩阵来实现一下(我们就以\(2*3\)的矩阵和\(3*2\)的矩阵为例):
\(\left[ \begin{matrix} a & b & c \\ d & e & f \end{matrix} \right] \times \left[ \begin{matrix} g & h \\ i & j \\ k & l \end{matrix} \right] = \left[ \begin{matrix} a*g+b*i+c*k & a*h+b*j+c*l \\ d*g+e*i+f*k & d*h+e*j+f*l \end{matrix} \right]\)
怎么样?是不是透彻了许多呢(要是还不透彻都对不起我手打的这个矩阵)
性质:
下面的东西可能会很乱,建议大家自己百度百科一下(有些东西貌似不用掌握,了解就好)
运算法则已经\(get\)到了吧,那这有啥特殊性质呢
- 矩阵乘法满足结合律,\(A*(B*C)=(A*B)*C\),根据这一性质,我们后面可以通过矩阵快速幂来快速求解,并且还能降低时间复杂度
- 矩阵乘法并不满足交换律,因为矩阵乘法涉及许多的操作,而且矩阵相乘得到的还是矩阵,总之就不满足交换律
我们记得逆元,其实每个元素\(a\)都存在唯一的逆元\(a^{-1}\),类似地,\(n\)阶矩阵(即\(n*n\))的 集合中也存在特殊的矩阵\(A\)存在唯一的逆元\(A^{-1}\)使得\(A*A^{-1}=A^{-1}*A=I_{n}\),\(A^{-1}\)就称为\(A\)的逆矩阵
其中的\(I_{n}\)为\(n\)阶的单位矩阵(其实就是对角线上全为1),我们可以形象地将单位矩阵理解为数字\(1\),因为用处的话到后面的矩阵快速幂十分显然
\(I_{n} = \left[ \begin{matrix} 1 & 0 & \cdots & 0 \\ 0 & 1 & \cdots & 0 \\ \vdots & \vdots & \ddots & \vdots \\ 0 & 0 & \cdots & 1 \end{matrix} \right]\)
- 矩阵可逆的一个常用的充分必要条件为:所有的行向量是线性无关的(线性无关是指任意一个向量都不是其他向量的线性组合)
- 矩阵\(A\)为\(n\)阶方阵,若存在\(n\)阶矩阵\(B\),使得矩阵\(A、B\)的乘积为单位阵,则称\(A\)为可逆阵,\(B\)为\(A\)的逆矩阵。若方阵的逆阵存在,则称为可逆矩阵或非奇异矩阵,且其逆矩阵唯一。
A可逆的充要条件:
1、\(|A|\)不等于0。
2、\(r(A)=n\)。
3、\(A\)的列(行)向量组i线性无关。
4、\(A\)的特征值中没有0。
5、\(A\)可以分解为若干初等矩阵的乘积。
其中\(r(A)\)为矩阵的秩:
用初等行变换将矩阵A化为阶梯形矩阵, 则矩阵中非零行的个数就定义为这个矩阵的秩, 记为\(r(A)\)
以下是满秩矩阵的定义:
若矩阵秩等于行数,称为行满秩;若矩阵秩等于列数,称为列满秩。既是行满秩又是列满秩则为n阶矩阵即n阶方阵。行满秩矩阵就是行向量线性无关,列满秩矩阵就是列向量线性无关;所以如果是方阵,行满秩矩阵与列满秩矩阵是等价的。
接下来为阶梯矩阵的定义:
矩阵的特征值:
设 A 是n阶方阵,如果存在数m和非零n维列向量x,使得 \(Ax=mx\) 成立,则称 m 是矩阵A的一个特征值或本征值
啊这,是不是吐了,我也是,我们还是愉悦地开始别的吧,这个要是没懂的话也不要紧,只要把单位矩阵之前的听懂就好了,感兴趣的可以自己下课搜一下百度百科
用途:
话说,讲了这么多,这个有什么用呢?
前面在说矩阵乘法满足结合律的时候提到了一个矩阵快速幂,是的,没错,就是这玩意,这玩意就是矩阵优化递推的原理,我们就可以把一个不能用快速幂优化的柿子转化成快速幂的形式
这样的话,我们就能把许多题的\(O(n)\)优化到\(O(log\ n)\)
前面我们提到了单位矩阵,我们说可以把单位矩阵形象地理解为数字\(1\),何处此言呢,我们考虑,我们要进行矩阵快速幂的话,肯定最后会找一个矩阵来承接,但这个矩阵长什么亚子呢?
我们考虑如果不做任何处理,直接用全为\(0\)的矩阵,那我们无论怎么乘,最后都会是\(0\)
但是单位矩阵就不一样了,我们来证明一下单位矩阵的合理性:
我们假设一个\(n\)阶矩阵\(A\),我们将\(A\)与\(I_{n}\)相乘,得到新矩阵\(C\)
\(\left[ \begin{matrix} a & b & c\\ d & e & f\\g & h & i \end{matrix} \right] \times \left[ \begin{matrix} 1 & 0 & 0\\0 &1 & 0\\ 0 & 0 & 1 \end{matrix} \right] = \left[ \begin{matrix} a & b & c \\ d & e & f\\ g & h & i \end{matrix} \right]\)
是不是惊奇地发现\(A\times I_{n} = C =A\)
我们考虑一下why?
因为我们发现当\(A\)与\(I_{n}\)相乘的时候,当\(A\)的第\(i\)行与\(I_{n}\)的第\(j\)列相乘得到新矩阵\(C\)的\(C_{i,j}\),我们可以想到\(I_{n}\)的第\(j\)列,一定只有第\(j\)个位置是\(1\),而其他位置均为\(0\),所以我们很容易知道\(A\)的第\(i\)行乘\(I_{n}\)的第\(j\)列,最后得到的那个数,一定是\(A_{i,j}\),而得到的这个值恰好是\(C_{i,j}\),所以就很神奇的证明出来了
突然发现矩阵的Markdown写起来要死人
我们透彻了原理的话,就要来做一道模板题了
这就是个矩阵快速幂的模板题,但要记得开长整型,并且写快速幂的时候要记得写\(while\)的,递归的会炸,你会死的很惨的
本来是想打重载的,但我太笨了,不会
参考代码:(这份代码写的有些忐忑,我居然开炸空间了,所以很丑,不要在意)
#include<bits/stdc++.h>
using namespace std;
const int P = 1e9 + 7;
long long n, k;
struct node{
long long a[105][105];
}ju, ans;
node musl(node op, node opp){
node res;
for(int i = 1; i <= n; i ++){
for(int j = 1; j <= n; j ++){
res.a[i][j] = 0;
}
}
for(int i = 1; i <= n; i ++){
for(int j = 1; j <= n; j ++){
for(int k = 1; k <= n; k ++){
res.a[i][j] = (res.a[i][j] + op.a[i][k] * opp.a[k][j] % P) % P ;
}
}
}
return res;
}
int main(){
scanf("%lld %lld", &n, &k);
for(int i = 1; i <= n; i ++){
for(int j = 1; j <= n; j ++){
scanf("%lld", &ju.a[i][j]);
if(i == j) ans.a[i][j] = 1;
}
}
while(k){
if(k & 1) ans = musl(ans, ju);
ju = musl(ju, ju);
k = k / 2;
}
for(int i = 1; i <= n; i ++){
for(int j = 1; j <= n; j ++){
printf("%lld ", ans.a[i][j]);
}
printf("\n");
}
return 0;
}
例题:
由于本人能力有限,找到的题不是很多,而且\(so\ easy\),所以,请各位\(dalao\)合理\(AC\)
斐波那契数列:
你在\(luogu\)搜斐波那契数列,你将会看到有好多颜色的,其中有道紫色的数据范围是\(n \leq 10^{30000000}, p<2^{31}\)
咳,当然我们不讲这个,因为我太菜了,这心有余而力不足呀!!!
我们考虑斐波那契数列的公式:
\(\left \{ \begin{array}{lrc} f_{1}= f_{2} =1 \\ f_{n}=f_{n-1} +f_{n-2}\ (n \geq 2 且 n\in Z)\end{array} \right.\)
我们考虑如何用矩阵得到呢
我们会发现\(f_{n}\)与\(f_{n-1},f_{n-2}\)有关
所以我们就设一个矩阵\(A=\left[ \begin{matrix} f_{n-1} & f_{n-2} \end{matrix} \right]\)
根据这个矩阵的话,我们最后肯定是要得到一个结构相似的,才能进行矩阵快速幂呢
我们不难发现我们可以得到矩阵\(C=\left[ \begin{matrix} f_{n} & f_{n-1} \end{matrix} \right]\)
那我们考虑\(A*B=C\)的话,那么这个\(B\)是啥呢
我们经过推理柿子:
\(\left[ \begin{matrix} f_{n-1} & f_{n-2} \end{matrix} \right] \times B = \left[ \begin{matrix} f_{n} & f_{n-1} \end{matrix} \right]\)
我们根据相乘的规律可以得到\(B\)一定是一个\(2*2\)的矩阵吧!
所以我们就要开始填数了
设\(B=\left[ \begin{matrix} x_{1} & x_{2} \\ x_{3} & x_{4} \end{matrix} \right]\)
由\(f_{n}= x_{1}*f_{n-1}+x_{3}*f_{n-2}=f_{n-1}+f_{n-2}\),我们不难得出\(\left \{ \begin{array}{lrc} x_{1}=1 \\x_{3}=1 \end{array} \right.\)
又因为\(f_{n-1}=x_{2}*f_{n-1}+x_{4}*f_{n-2}\),我们可以得到\(\left \{ \begin{array}{lrc} x_{2}=1\\x_{4}=0 \end{array} \right.\)
所以我们就完美地求出了矩阵\(B=\left[ \begin{matrix} 1 & 1\\1 & 0 \end{matrix} \right]\)
参考代码:(太丑了,我自己都嫌弃这份代码)
#include<bits/stdc++.h>
using namespace std;
const int p = 1e9 + 7;
struct node{
long long a[3][3];
}js, s;
struct nndd{
long long a[3];
}ans, st;
node mul(node op, node opp){
node ss;
for(int i = 1; i <= 2; i ++){
for(int j = 1; j <= 2; j ++){
ss.a[i][j] = 0;
}
}//清空一下下
for(int i = 1; i <= 2; i ++){
for(int j = 1; j <= 2; j ++){
for(int k = 1; k <= 2; k ++){
ss.a[i][j] = (ss.a[i][j] + op.a[i][k] * opp.a[k][j] % p) % p;
}
}
}//矩阵乘法基本操作
return ss;
}
long long n;
int main(){
scanf("%lld", &n);
if(n == 1 || n == 2){//特判
printf("1\n");
}else{
st.a[1] = st.a[2] = 1;//st存的是f[2],f[1]
js.a[1][1] = js.a[1][2] = js.a[2][1] = 1;//要把那个循环的东西搞出来
s.a[1][1] = s.a[2][2] = 1;//这个就是单位矩阵,最后承接js的辣么多次方
n = n - 2;//我们要算js的(n-2)次方,所以直接(n=n-2)就okkk了
while(n){
if(n & 1) s = mul(js, s);
js = mul(js, js);
n >>= 1;
}//这就是矩阵快速幂
for(int i = 1; i <= 2; i ++){
for(int j = 1; j <= 2; j ++){
ans.a[i] = (ans.a[i] + st.a[i] * s.a[j][i] % p) % p;
}//将答案转移到ans数组
}
printf("%lld\n", ans.a[1]);
}
return 0;
}
刷题比赛:
是的呢,你没有看错,就是上次\(ZQH\)学长讲的那个\(11*11\)的恶心的矩阵
咳!又到了推柿子的环节了(又要敲矩阵了)
首先这三个人\(nodgd,Ciocio,Nicole\)
(\(zcl,Vanyun,Aonrbet\))
他们第\(k+2\)天的刷题量分别为
\(a_{k+2}=p*a_{k+1}+q*a_{k}+b_{k+1}+c_{k+1}+r*k^{2}+t*k+1\\ b_{k+2}=u*b_{k+1}+v*b_{k}+a_{k+1}+c_{k+1}+w^{k}\\ c_{k+2}=x*c_{k+1}+y*c_{k}+a_{k+1}+b_{k+1}+z^{k}+k+2\)
这其中我们不难发现\(k\)是一个变量,那肯定就会和矩阵有点关系,我们先将含\(k\)的元素列举出来
$a_{k+1},a_{k},b_{k+1},b_{k},c_{k+1},c_{k},k{2},k,w,z^{k} $
但是我们考虑一下当\(k^{2}\)变为\((k+1)^{2}=k^{2}+2*k+1\)的时候,我们还会需要到\(1\),所以我们把\(1\)加入到这个矩阵中,我们就得到了这个\(1*11\)的矩阵:
\(\left[ \begin{matrix} a_{k+1} & a_{k} & b_{k+1} & b_{k} & c_{k+1} & c_{k} &k^{2}& k & w^{k} & z^{k} & 1\end{matrix} \right]\)
我们考虑要得到的矩阵是什么亚子的呢
\(\left[ \begin{matrix} a_{k+2} & a_{k+1} & b_{k+2} & b_{k+1} & c_{k+2} & c_{k+1} & (k+1)^{2} & k+1 & w^{k+1} & z^{k+1} & 1\end{matrix} \right]\)
既然\(A*B=C\)中\(A,C\)都已经明确了,那我们就求一下中间的转移矩阵吧
我们通过计算可以得知矩阵\(B\)一定是一个\(11*11\)的矩阵
那我们根据一堆乱七八糟的东西可以推出来这个转移矩阵(敲得我手疼)
\(\left[ \begin{matrix} p & 1 & 1 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 \\ q & 0 & 0& 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ 1 & 0 & u & 1 & 1 & 0 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & v & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ 1 & 0 & 1 & 0 & x & 1 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & y & 0 & 0 & 0 & 0 & 0 & 0 \\ r & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 \\ t & 0 & 0 & 0 & 1 & 0 & 2 & 1 & 0 & 0 & 0 \\ 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & w & 0 & 0 \\ 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & z & 0 \\ 1 & 0 & 0 & 0 & 2 & 0 & 1 & 1 & 0 & 0 & 1 \end{matrix} \right]\)
但这道题还是个要注意的地方:这东西算出来特别~~大,所以要用到我们上面讲到的龟速乘
这道题反正就觉得挺恶心的,但是把矩阵写出来就好多了
还有就是提醒广大同胞们,龟速乘那里是乘\(2\),不是乘它本身,不要像sb的我一样在这卡了一个小时
参考代码:
#include<bits/stdc++.h>
using namespace std;
long long n, m;
int p, q, r, t, u, v, w, x, y, z;
struct node{
long long a[12][12];
}s, ss, sss, ans;
long long lower_mul(long long x, long long y){
long long res = 0;
while(y){
if(y & 1) res = (res + x) % m;
x = (x * 2) % m;
y >>= 1;
}
return res % m;
}
node musl(node op, node opp){
node res;
for(int i = 1; i <= 11; i ++){
for(int j = 1; j <= 11; j ++){
res.a[i][j] = 0;
}
}
for(int i = 1; i <= 11; i ++){
for(int j = 1; j <= 11; j ++){
for(int k = 1; k <= 11; k ++){
res.a[i][j] = (res.a[i][j] + lower_mul(op.a[i][k], opp.a[k][j])) % m;
}
}
}
return res;
}
int main(){
scanf("%lld %lld", &n, &m);
scanf("%d %d %d %d %d %d %d %d %d %d", &p, &q, &r, &t, &u, &v, &w, &x, &y, &z);
s.a[1][1] = 3, s.a[1][2] = 1, s.a[1][3] = 3;
s.a[1][4] = 1, s.a[1][5] = 3, s.a[1][6] = 1;
s.a[1][7] = 1, s.a[1][8] = 1, s.a[1][9] = w;
s.a[1][10] = z, s.a[1][11] = 1;
for(int i = 1; i <= 11; i ++) sss.a[i][i] = 1;
ss.a[1][1] = p, ss.a[2][1] = q, ss.a[3][1] = 1;
ss.a[5][1] = 1, ss.a[7][1] = r, ss.a[8][1] = t;
ss.a[11][1] = 1, ss.a[1][2] = 1, ss.a[1][3] = 1;
ss.a[1][5] = 1, ss.a[3][3] = u, ss.a[3][4] = 1;
ss.a[3][5] = 1, ss.a[4][3] = v, ss.a[5][3] = 1;
ss.a[5][5] = x, ss.a[5][6] = 1, ss.a[6][5] = y;
ss.a[7][7] = 1, ss.a[8][5] = 1, ss.a[8][7] = 2;
ss.a[8][8] = 1, ss.a[9][3] = 1, ss.a[9][9] = w;
ss.a[10][5] = 1, ss.a[10][10] = z, ss.a[11][5] = 2;
ss.a[11][7] = 1, ss.a[11][8] = 1, ss.a[11][11] = 1;
n = n - 2;
while(n){
if(n & 1) sss = musl(sss, ss);
ss = musl(ss, ss);
n = n/2;
}
for(int j = 1; j <= 11; j ++){
for(int k = 1; k <= 11; k ++){
ans.a[1][j] = (ans.a[1][j] + lower_mul(s.a[1][k], sss.a[k][j])) % m;
}
}
printf("nodgd %lld\nCiocio %lld\nNicole %lld\n", ans.a[1][1], ans.a[1][3], ans.a[1][5]);
return 0;
}
我今天要讲的就这些,应该不难,那个矩阵乘法还有一些优化啥的在我甩的那个链接里
emmmmm....就这样吧
补充:
取模运算重要性质:
模运算与基本四则运算有些相似,但是除法例外。其规则如下:
- $(a + b)% p = (a% p + b% p)% p $
- $(a - b)% p=(a% p - b% p)% p $
- \((a * b)\% p=(a\% p * b\% p)\% p\)
- \(a^b\% p =((a\% p)^b)\% p\)
- 结合律:
-
\(((a+b)\% p + c)\% p = (a + (b+c)\% p)\% p\)
-
\(((a*b)\% p * c)\% p = (a * (b*c)\%p)\% p\)
- 交换律:
-
\((a + b)\% p = (b+a)\% p\)
-
\((a * b)\% p = (b * a)\% p\)
- 分配律:
-
\((a+b)\% p = ( a\% p + b\% p )\% p\)
-
\(((a +b)\% p * c)\% p = ((a * c)\% p + (b * c)\% p)\% p\)
【重要定理】
- 若\(a\equiv b\ (mod\ p)\),则对于任意的\(c\),都有\((a+c)\equiv (b+c)\ (mod\ p)\)
- 若\(a\equiv b\ (mod\ p)\),则对于任意的\(c\),都有\((a*c)\equiv (b*c)\ (mod\ p)\)
- 若\(a\equiv b\ (mod\ p),c\equiv d\ (mod\ p)\),则\((a+c)\equiv (b+d)\ (mod\ p),(a-c)\equiv (b-d)\ (mod\ p),(a*c)\equiv (b*d)\ (mod\ p)\);
欧拉定理证明:
我的数论不是特别的好,所以我尽量让大家透彻欧拉定理
首先我们应该还记得上面我讲费马小定理的时候用到的引理一和引理二吧
我们考虑一下欧拉定理的结论是啥吧
若\(p,a\)为正整数,且\(p,a\)互质,则:\(a^{\varphi(p)}\equiv 1\ (mod\ p)\)
首先,我们取出\(a\)在模\(p\)意义下,一个完全剩余系中所有与\(p\)互质的数,
记为\(k_{1},k_{2},\cdots,k_{\varphi(p)}\),并且\(k_1,k_{2},\cdots, k_{\varphi(p)}\)在同一个完全剩余系中,这样就构成了模\(p\)的简化剩余系,简称缩系
我们可以知道\(k_{i}\)与\(p\)互质
因为\(p,a\)互质
所以根据引理二,我们可以知道\(a*k_{1},a*k_{2},\cdots, a*k_{\varphi(p)}\)也是模\(p\)意义下的一个剩余系
根据取模运算的定理,我们又不难发现这个剩余系是一个缩剩余系
至于为什么呢?听我细细道来,我们考虑\(k_{i}\)是一个缩系,里面的每个元素都与\(p\)互质,
互质的话,说明\(k_{i}\)的每个元素的唯一分解中一定不会出现\(p\)的唯一分解中出现的质数,否则,这两个数的\(gcd\)肯定不为\(1\),即这两个数一定不互质,那我们把\(k_{i}\)的每个元素都乘一个新的\(a\),这个\(a\)也是与\(p\)互质的一个元素,我们可以把\(k_{i}*a\)想象成为在\(k_{i}\)的每个元素的唯一分解中都加入\(a\)的唯一分解的质数,那么我们能够知道,\(k_{i}*a\)依旧是与\(p\)互质的,而且一定还是一个完整的缩系,虽然可能不与原来的\(k_{i}\)相对应,那为什么一定会是一个完整的新的缩系呢?
我们用反证法,假设存在\(a*k_{i} \equiv a*k_{j}\ (mod\ p)\ (i!=j)\),那我们根据推理一可以得到\(k_{i} \equiv k_{j}\ (mod\ p)\),这显然是不成立的,所以,我们可以得知\(a*k_{i}\)在模\(p\)的意义下肯定是不同余的,所以会构成一个完整的新的缩系
∴\(k_1*k_{2}*\cdots*k_{\varphi(p)} \equiv a*k_{1}*a*k_{2}*\cdots \ a*k_{\varphi(p)}\ (mod\ p)\)
∴\(k_{1}*k_{2}*\cdots *k_{\varphi(p)} \equiv a^{\varphi(p)} * k_{1}*k_{2} *\cdots * k_{\varphi(p)}\ (mod\ p)\)
然后根据引理一我们两边同时消去\(k_{1}*k_{2}*\cdots *k_{\varphi(p)}\),我们会得到\(a^{\varphi(p)} \equiv 1\ (mod\ p)\)
证毕。
是不是透彻了许多呢,那我们就来了解一下拓展欧拉定理吧
费马小定理的欧拉定理证明:
首先上面我们已经知道了欧拉定理的正确性
然后我们考虑当\(p\)为质数时,\(\varphi(p)=p-1\),所以我们可以知道当\(p\)为质数的时候\(a^{\varphi(p)}=a^{p-1} \equiv 1\ (mod\ p)\)
所以我们的费马小定理就得到了证明
拓展欧拉定理
首先我们要知道拓展欧拉定理是个啥玩意吧!!!
\(\left \{ \begin{array}{lrc} a^{c}\equiv a^{c\ mod\ \varphi(m)}\ (mod\ m)\ (gcd(a,m)=1)\\ a^{c}\equiv a^{c}\ (mod\ m)\ (gcd(a,m)\neq 1 且 c < \varphi(m))\\a^{c}\equiv a^{c\ mod\ \varphi(m) + \varphi(m)}\ (mod\ m)\ (gcd(a,m)\neq1且c\geq \varphi(m)) \end{array} \right.\)
(1).当\(gcd(a,m)=1\)的时候,我们可以设\(c=k*\varphi(m)+d(d=c\ mod\ \varphi(m))\),我们可以得到\(a^{k*\varphi(m)+d}\equiv a^{d}\ (mod\ m)\),即\(a^{k*\varphi(m)}\times a^{d}\equiv a^{d}\ (mod\ m)\)
又因为\(a^{\varphi(m)}\equiv 1\ (mod\ m )\),所以\(a^{k*\varphi(m)}\equiv 1\ (mod\ m)\)
我们可以把\(a^{k* \varphi(m)}\),看成\(k\)个\(a^{\varphi(m)}\)相乘,那最后还是会\(a^{k*\varphi(m)}\equiv 1\ (mod\ m)\)
然后我们两边同时乘上\(a^{m}\),得到\(a^{k*\varphi(m)}\times a^{d}\equiv a^{d}\ (mod\ m)\)
即我们要证的\(a^{c}\equiv a^{c\ mod\ \varphi(m)}\ (mod\ m)\)
(2).这很显然的事情\(a^{c}\ mod\ m=a^{c}\ mod\ m\)
(3).首先我们知道\(gcd(a,m)\neq1\)即\(a,m\)的 唯一分解中存在相同的质因数
我们首先取出\(m\)的一个质因数\(p\),令\(m = p^{r}*s\)
因为\(s\)中不含\(p\),所以\(gcd(p,s)=1\)
根据欧拉定理我们可以知道\(p^{\varphi(s)}\equiv 1\ (mod\ s)\)
根据欧拉函数的性质可以得到\(\varphi(m)=\varphi(p^{r})*\varphi(s)\)(前提是\(gcd(p^{r},s)=1\))
所以\(p^{\varphi(p^r)\times \varphi(s)}\equiv 1\ (mod\ s)\)
我们可以得到\(p^{\varphi(m)}\equiv 1\ (mod\ s)\)
我们还是可以想象成\(\varphi(p^ r)\)个\(\varphi(s)\)相乘,那么最后还是\(p^{\varphi(p^r)\times \varphi(s)}\equiv 1\ (mod\ s)\)
即\(p^{\varphi(m)}\equiv 1\ (mod\ s )\)
我们可以设\(p^{\varphi(m)} =k*s+1\),所以\(p^{\varphi(m)+r}=p^{\varphi(m)}*p^r=(k*s+1)*p^r=k*m+p^r\)
所以我们可以知道\(p^{\varphi(m)+r}\equiv p^r\ (mod\ m)\)
所以我们可以发现\(p^{c-r}*p^{\varphi(m)+r}\equiv p^r *p^{c-r}\ (mod\ m)(c \geq r)\)
没有写完呢,就(甩个链接)[https://blog.bill.moe/euler-theorem-notes/]吧
https://www.seotest.cn/jishu/43916.html
乘法逆元二(第一篇题解的方法)
来看这道我前前后后一共交了40遍的\(sb\)题
这道题引起了我的极度不适,愿你们能平安AC,我太菜了
我们首先考虑这道题最后要得到的答案是\(\displaystyle \sum_{i = 1}^{n}{\frac {k^{i}}{a_{i}}}\)
我们设\(\displaystyle s=\prod _{i = 1}^{n}{a_{i}}\),然后我们会发现\(\frac {1}{a_{i}}=\frac {s/{a_{i}}}{s}\)
然后柿子就变成了\(\displaystyle \sum _{i = 1}^{n}{\frac {k^{i}*(s/a_{i})}{s}}\)
∴我们只要维护一下当前这个位置的\(s/a_{i}\),即维护一个前缀积和一个后缀积就欧克了
我们考虑每次都要乘\(\frac 1{s}\),我们这个提出来就变成了\(\displaystyle \frac 1{s}*\sum_{i=1}^{n}{k^{i}*(s/a_{i})}\)
我们考虑\(\frac 1{s}=s^{-1}\),所以我们最后把\(\displaystyle \sum _{i = 1}^{n}{k^i*(s/a_i)}\)求出来之后,然后乘上\(s\)的逆元就欧克了
整体的复杂度是\(O(n + log\ p)\) 题解给的是\(O(n)\)
经过我\(n+n\)次的试验,第一篇题解的线性求单个逆元的时间居然优于费马小定理
(我又交了好多次,其中有两次是求单个逆元的方法不同,线性的方法能AC,而费马小定理的T了好几个点)
或许是因为\(ksm\)要申请新的空间吧
应该透彻了吧
下面的代码不一定能AC,因为\(luogu\)的测评机有点双标,相同的代码我交了两次,第一遍AC,第二遍T了两个点
参考代码:
#include<cstdio>
#include<iostream>
using namespace std;
const int N = 5e6 + 5;
int n, p;
long long k;
int a[N];
long long pre[N], nxt[N], ans;
inline long long inv(int x){
if(x == 1) return 1;
else return (p - p / x) * inv(p % x) % p;
}
inline int read(){
int op = 0, opp = 1; char ch = getchar();
while(ch > '9' || ch < '0'){ if(ch == '-') opp = -1; ch = getchar();}
while(ch >= '0' && ch <= '9'){ op = (op << 1) + (op << 3) + (ch - '0'); ch = getchar();}
return 1ll * op * opp;
}
int main(){
n = read(), p = read(), k = read();
pre[0] = nxt[n + 1] = 1;
for(int i = 1; i <= n; i ++){
a[i] = read();
pre[i] = 1ll * pre[i - 1] * a[i] % p;
}
for(int i = n; i >= 1; i --){
nxt[i] = 1ll * nxt[i + 1] * a[i] % p;
}
int t = k;
for(int i = 1; i <= n; i ++){
ans = (ans + k * pre[i - 1] % p * nxt[i + 1] % p )% p;
k = 1ll * k * t % p;
}
printf("%d\n", ans * inv(pre[n]) % p);
return 0;
}