逆元
扯一点没有多大用的东西
在数论里面,我们不把倒数叫做倒数,而叫做逆元(纯属装13)
逆元的作用很大,我们来看点easy的栗子
某些性质
前面三个都是对的
(不需要证明~)但——
(÷是整除)
- 随便举个例子就知道它不成立
随便搞一下
这是不是说我们对除法下的大数模操作就毫无办法了?naive
为了方便,先来看看一些小学or初中级别的东东
有条等式
- 明显 ,我再加一个条件
那么现在 就不一定等于 了
对于①,我们把 看成 ,而②式加了一个同余
- 这时我们不把 看成倒数,这时的x为a关于p的逆元
(注意,当且仅当
且p为质数时,x才存在)
又是一个栗子
- 在这里, 和 的作用是一样的(乘3再模7都是1),所以,5是3关于7的逆元
注意一点,除了 ,还有其他整数 满足 ,如 等
连百度都说逆元唯一,但是逆元不是唯一的!!!(感谢同学指出)
逆元的卵用
前面已经说了一些东东了
相信逆元的定义很好懂
我们不妨把 的逆元用 来表示,那么对于除法——
难搞的除法瞬间变成了乘法,变水了很多
逆元这东东怎么求
way one
基础数论里面有两条著名的定理:欧拉定理和费马小定理
欧拉定理:(当 时成立)
费马小定理:(当 且p为质数时成立)
(我难道会告诉你我两个都不会证明吗……)
明显,费马小定理是可以从欧拉定理推过来的
当p为质数时 ,代入欧拉定理即费马小定理
我们在谈论逆元,就把费马小定理两边同除以一个p
注意到一点就是 是个小数,而等号左边是个整数,别忘了我们在说的是数论倒数
想一想,我们发现应该把 写成inv(a)——
- so——
我们可以用快速幂来求a的逆元,时间复杂度
code
long long inv(long long a,long long p)
{
long long t=1,b=p-2;
while (b)
{
if ((b%1)==1)t=t*a%p;
a=a*a%p;
b/=2;
}
return t;
}
通常题目会让你求
取模,快到飞起
快速幂求逆元,对于大多题目,够了
way two
逆元还可以用扩展欧几里得来求
首先要知道贝祖定理,描述是
对于 且 ,必然存在整数x和y,使得
比如我们现在要求 关于 的逆元(条件是 和 互质且 是质数)
代入贝祖定理,则
给上面那个式子做点操作,两边各模一个 ,得到
∴x是a关于p的逆元(其实 也是 关于 的逆元,可证,同理)
这里用扩展欧几里得 ,求出的 即为 的逆元
code
void exgcd(long long a,long long b,long long &x,long long &y,long long &z)
{
if (!b)
{
z=a;
x=1;
y=0;
return;
}
exgcd(b,a%b,y,x,z);
y-=x*(a/b);
}
long long inv(long long a,long long b)
{
long long x,y;
exgcd(a,b,x,y);
return (x%p+p)%p;
}
扩展欧几里得可谓比较牛,优点是用途多,能求gcd能求逆元能解线性方程
对于广大OIers是把利器,学会是必须的
way three
最后一种方法更简单更好打更好理解:递归
先来一发证明网上找的
有了这个,我们就可以用递归来求 了
递归出口 ,因为 的逆元就是
递归code
long long inv(long long a,long long p) //注意a要小于p,先把a%p一下
{
return a==1?1:(p-p/a)*inv(p%a,p)%p;
}
是不是短到爆炸?
因为 后至多是 ,所以时间复杂度是
这种方法还有一个好处,能用 的时间预处理1~n的逆元
只不过把递归的式子改成递推而已
递推code
void init()
{
inv[1]=1;
for(int i=2;i<=n;i++)
{
inv[i]=(p-p/i)*1ll*inv[p%i]%p;
}
}
个人感觉方法三在某些题目里更好用一些
易理解,码量低,时间复杂度也低,还能快速预处理
因吹斯听
:D
逆元没得好说了
三种方法都是求所有逆元中小于 的那个
其实都挺好用的虽说我倾向于第三种,这个东西多少随意
欸扩展欧几里得是什么?顺便看看?
扩展欧几里得
先复习一下普通欧几里得算法吧
普通欧几里得算法又称辗转相除法,为
我TM傻到连证明也要CO了证明没什么必要,
图个开心罢了
证明一发?
先设
可以表示成 ( ),
设 是 和 的一个公约数,那么
把 两边各除以 ,得到
为整数, 也就是
是 和 的公因数
设 是 和 的公因数中的任意一个
,设 ,
, ,就是说 的所有公因数是一样的
公因数相同,最大公因数不也就相同了?
code
long long gcd(long long x,long long y)
{
return !y?x:gcd(y,x%y);
}
每 一次,大的那个数至多是原来一半
所以时间复杂度为
貌似欧几里得平均迭代次数为 耶……随意……
还是要学扩欧的
不会
扩欧就是已经知道 ,求解能使该等式成立的一组
明显的啊,想要有整数解的话,必须满足
不然两边各除以 ,等号左边是整数,而等号右边就是个分数了→_→您太强啦
不知道怎么求就右上角
设
时,明显
但当 时,设
根据某恒等定理所以 和 的值是由 和 来决定的
和 的值通过递归得到
如此完美
code
long long exgcd(long long a,long long b,long long &x,long long &y)
{
if (!b)
{
x=1,y=0;
return a;
}
long long ans=exgcd(b,a%b,x,y);
int temp=x;
x=y;
y=temp-a/b*y;
return ans;
}
其实差不多就是从上面CO过来的
直接调用
exgcd(a,b,x,y)
,结果是返回同时求出来的 和 能干嘛呢?
扩欧?有什么用?
扩欧一共有三种用途
purpose one
purpose two
purpose three
类欧几里得
随意随意
类欧几里得也是一种算法啦
它的样子很像欧几里得算法,所以叫做类欧
它可以解决 时间的问题,然而它的时间复杂度和欧几里得是一样的,都是
下面进入类欧的
奇幻♂世界
三个函数和一个m
看官随便看看
反正类欧可以把这三个
明显的东东用 搞掉
您也知道鄙人数论不好,所以这坑要多久填完我不好说
下面的除法都看成整除,不再添加 了,每个除法都打一次太eggsache了
求f
- 分类讨论!
时
或 时
且 时
莫比乌斯反演
都说了定义没用的还不信
莫比乌斯反演是数论数学中很重要的内容
在许多情况下能够简化运算,可以用于解决很多组合数学的问题
这没个卵用
反正是个很奇妙的东西
并没有在网上找到莫比乌斯反演的详细解释
不墨迹了
懵逼乌斯繁衍
定义
莫比乌斯反演就是在已知 的情况下反演求
我们可以试着用 来表示 ,则有
我不会证明是什么呢?
关于
不必知道 是什么东西
这只是我们自己YY出来的、觉得这里应该有的一个函数只需要知道 就是莫比乌斯函数
推一波 可以发现一些东东
白内障看不清就放大看得出
其实 就是
容易发现 只能取
更准确地给 来取值,那么
上面 为互质的质数,这个应该看得懂
暴力求 肯定很慢,怎么快速求 呢?
注意 的性质
的性质一
不会证
这个东西对接下来推式子有很大的帮助
必须记住
求
性质二:是积性函数,但不是完全积性
思考对于数 ,若 为质数则
若 ,则 有平方因子,所以
否则
code
线筛里面 都可以求出来
void init()
{
memset(bz,1,sizeof(bz));
mu[1]=phi[1]=1,tot=0;
for (int i=2;i<MAXN;i++)
{
if (bz[i])p[tot++]=i,phi[i]=i-1,mu[i]=-1;
for (int j=0;j<tot && i*p[j]<=MAXN;j++)
{
bz[i*p[j]]=0;
if (i%p[j]==0)
{
mu[i*p[j]]=0;
phi[i*p[j]]=phi[i]*p[j];
break;
}
mu[i*p[j]]=-mu[i],phi[i*p[j]]=phi[i]*(p[j]-1);
}
}
for (int i=1;i<MAXN;i++)pre[i]=pre[i-1]+mu[i];//μ的前缀和,会有用的
}
这种数学的东东还是要草稿纸什么才行
直接上反演公式套路
默认下面的 小于 ,除都是整除
繁衍套路1
求
推倒
想到性质一也就是
把它放进上面的式子里面
想一下 出现了多少次呢? 次!!
所以
- 为了美观把 换成 ,下面不再强调了,最后
暴力只能 求了,但是我们可以分块做到根号复杂度
先看套路2
繁衍套路2
求
推倒
和套路1差不多
根据容斥,同时除上一个
- 然后根据我们已经推出来了的
暴力时间复杂度
开始分块
我们容易知道的是, 最多只有 个取值
(不会证)同理 最多也只有 个取值,所以 和 同时不变的段数有 个
先看一幅图
(网上盗的)这里 ,红色为 和 不变的整段
看懂了么?
既然这些段 , 都不变,意味什么?
我们取 的前缀和,然后拿每一段的最后一位的 的前缀和乘上 不就是这一段的答案和么?
分块时间复杂度
code
int get(int n,int m,int d)
{
if (n>m)swap(n,m);
int ans=0;
n/=d,m/=d;
for (int i=1,last=1;i<=n;i=last+1)
{
last=min(n/(n/i),m/(m/i));
ans+=(pre[last]-pre[i-1])*(n/i)*(m/i);
}
return ans;
}
例题1:【BZOJ2301】 [HAOI2011]Problem b
problem
analysis
反演例题
由容斥可得,
单分块还不会?上面刚刚推出来的
于是每一个分块都是 的时间,总 时间复杂度
code
#include<stdio.h>
#include<string.h>
#include<algorithm>
#define MAXN 10000001
using namespace std;
bool bz[MAXN];
int p[MAXN],phi[MAXN],mu[MAXN],pre[MAXN];
int n,a,b,c,d,k,tot;
int read()
{
int x=0,f=1;
char ch=getchar();
while (ch<'0' || '9'<ch)
{
if (ch=='-')f=-1;
ch=getchar();
}
while ('0'<=ch && ch<='9')
{
x=x*10+ch-'0';
ch=getchar();
}
return x*f;
}
void init()
{
memset(bz,1,sizeof(bz));
mu[1]=phi[1]=1,tot=0;
for (int i=2;i<MAXN;i++)
{
if (bz[i])p[tot++]=i,phi[i]=i-1,mu[i]=-1;
for (int j=0;j<tot && i*p[j]<=MAXN;j++)
{
bz[i*p[j]]=0;
if (i%p[j]==0)
{
mu[i*p[j]]=0;
phi[i*p[j]]=phi[i]*p[j];
break;
}
mu[i*p[j]]=-mu[i],phi[i*p[j]]=phi[i]*(p[j]-1);
}
}
for (int i=1;i<MAXN;i++)pre[i]=pre[i-1]+mu[i];
}
int get(int n,int m,int d)
{
if (n>m)swap(n,m);
int ans=0;
n/=d,m/=d;
for (int i=1,last=1;i<=n;i=last+1)
{
last=min(n/(n/i),m/(m/i));
ans+=(pre[last]-pre[i-1])*(n/i)*(m/i);
}
return ans;
}
int main()
{
//freopen("readin.txt","r",stdin);
init();
n=read();
while (n--)
{
a=read(),b=read(),c=read(),d=read(),k=read();
printf("%d\n",get(b,d,k)-get(a-1,d,k)-get(b,c-1,k)+get(a-1,c-1,k));
}
return 0;
}
繁衍套路3
求
推倒
- 枚举约数
- 还是再加一个 进去再变形