C. C. Blog

Security Research, Algorithm and Data Structure

在做各种取余运算时,我们会发现先除和先取余的结果是不同的,这会导致计算难度的加大,乘法逆元可以解决类似的分数取余问题。

本文按照博主的学习思路排序,结论为(单个)费马小定理求乘法逆元(单个)利用扩展欧几里得任意情况下求乘法逆元(批量)线性递推求乘法逆元

同余的运算法则之一

定理3 ac≡bc(mod m),且若(m,c)≡d,则a≡b(mod m/d)。——数论讲义

也就是说,同余式两边可以同除一个和模数互质的数(此时模数不变)。

费马小定理

形式1:若m是素数,则a^m≡a(mod m)。——数论讲义······①

形式2:如果m是一个素数,而整数a不是m的倍数,则有a^(m-1)≡1(mod m)。——某百科······②

由于m是一个素数,因此①到②的条件:除数a和模数m互质,等价于a不是m的倍数。

费马小定理②的推论

a^(m-1)≡1(mod m) => a*a^(m-2)≡1(mod m) => a^(m-2)≡1/a(mod m)

乘法逆元

对于正整数a和m,如果有ax≡1(mod m),那么把这个同余方程中x的最小正整数解叫做a模m的逆元。

若a与m不互质,易证ax%m!=1,故a必与m互质

乘法逆元与费马小定理的推论

如果m是素数,有以下推论:

ax≡1(mod m) => x≡1/a(mod m) => x≡a^(m-2)(mod m)

(同余的传递性)

(单个)费马小定理求乘法逆元

计算(a/b)%m:

m是素数

将1/b替换成逆元x,即b^(m-2)%m。

答案为(a*b^(m-2))%m。

例题

51Nod 1013

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define db double
#define mod 1000000007
ll n;
ll speed(ll a,ll b,ll p){
ll cur=a,ans=1;
while(b){
if(b&1) ans=ans*cur%p;
cur=cur*cur%p;
b>>=1;
}
return ans%p;
}
int main(){
cin>>n;
ll ans=((speed(3,n+1,mod)-1)*speed(2,mod-2,mod))%mod;
cout<<ans<<endl;
//cout<<speed(2,mod-2,mod)%mod;
return 0;
}```



**为了方便求任意情况的逆元,我们先来补充几个概念:**

## 欧几里得算法

log时间内求解最大公约数。

### 代码

```c++
ll gcd(ll a,llb){
return b==0?a:gcd(b,a%b);
}```

### 证明

[辗转相除法求最大公约数算法证明](https://www.cnblogs.com/zwffff/archive/2010/08/25/1808178.html "【整理】辗转相除法求最大公约数算法证明 - 凌风有约 - 博客园")

## 贝祖定理

设a,b是整数,则存在整数x,y,使得ax+by=gcd(a,b)

## 扩展欧几里得算法

#### 求贝祖定理中x和y的解,即求使ax+by=gcd(a,b)的x和y。

我们利用gcd来求,当递归到最内层时,a=gcd(a,b),b=0,因此x=1,y=0

假设我们已经得到内层x1,y1,对于外层a,b有a%b=a-(a/b)*b

外层:ax+by=gcd(a,b)

内层:bx1+(a-(a/b)b)y1=gcd(a,b)

1
2
3

展开对比可得:

x=y1 y=x1-a/by1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

### 代码

```c++
ll exgcd(ll a,ll b,ll &x,ll &y){//扩展欧几里得算法
if(b==0){//递归边界
x=1;y=0;
return a;
}
ll ret=exgcd(b,a%b,x,y);
ll tmp=y;//求解原x,y
y=x-a/b*y;
x=tmp;
return ret;//返回gcd
}

参考

扩展欧几里得算法详解

(单个)利用扩展欧几里得任意情况下求乘法逆元

计算(a/b)%m:

仍需要逆元存在,即b与m互质。

构造bx+my=gcd(b,m)=1,用exgcd求出来的x即为逆元。

代码与前面相同。

注意这里参数传m和-m都一样,答案要加m再%m。

参考

欧几里得算法心得(辗转相除法)

(批量)线性递推求乘法逆元

给定n,p求1~n中所有整数在模p意义下的乘法逆元。

n为3e6,p>n且为质数。

洛谷P3811 【模板】乘法逆元

此时用单个求法会超时,我们可以推导递推公式。

本垃圾博主还不会用公式编辑器,于是看这里↓

线性求逆元 - Grary - 博客园

试了试,这里p也必须是质数,还不知道为什么(逃

代码

c++ #include<bits/stdc++.h> using namespace std; #define ll long long const ll MAXN=3e6+20; ll inv[MAXN]; ll n,mod; void preinv(ll num,ll p){ inv[0]=inv[1]=1; for(ll i=2;i<=num;i++){ inv[i]=(p-p/i)*inv[p%i]%p; } } int main(){ cin>>n>>mod; preinv(n,mod); for(ll i=1;i<=n;i++){ printf("%lld\n",inv[i]); } return 0; }

题目

有一个无限大的蜂巢迷宫,为了方便表示每一个六边形格子,现在把座标引入到这个迷宫中,如上图年示。

艾瑞特在这个迷宫中街,刚开始他在(0,0)的位置,按照下图所示的路线在这个迷宫中行走。

走了n步以后,他想知道自己在哪个位置了。

思路

走1-6步1层,7-18步2层,二分查找层数,在最后一层6个if走6边

代码

c++ #include<bits/stdc++.h> #define ll long long #define db double using namespace std; ll n; int main(){ cin>>n; if(n==0){ cout<<"0 0\n"; return 0; } ll l=1,r=1e9,mid,lev; while(l<=r){ mid=(l+r)>>1; if(3*mid*(mid-1)<n){ l=mid+1; lev=mid; } else r=mid-1; } //cout<<lev<<endl; ll cx=-1+2*lev,cy=2; n-=3*lev*(lev-1);n--; if(n<=lev-1){ cx-=n,cy+=2*n; cout<<cx<<' '<<cy<<endl; return 0; } n-=(lev-1); cx-=(lev-1),cy+=2*(lev-1); if(n<=lev){ cx-=2*n; cout<<cx<<' '<<cy<<endl; return 0; } n-=lev; cx-=lev*2; if(n<=lev){ cx-=n,cy-=2*n; cout<<cx<<' '<<cy<<endl; return 0; } n-=lev; cx-=lev,cy-=2*lev; if(n<=lev){ cx+=n,cy-=2*n; cout<<cx<<' '<<cy<<endl; return 0; } n-=lev; cx+=lev,cy-=2*lev; if(n<=lev){ cx+=2*n; cout<<cx<<' '<<cy<<endl; return 0; } n-=lev; cx+=2*lev; if(n<=lev){ cx+=n,cy+=2*n; cout<<cx<<' '<<cy<<endl; return 0; } n-=lev; cx+=lev,cy+=2*lev; return 0; }

题目

有一口井,井的高度为N,每隔1个单位它的宽度有变化。现在从井口往下面扔圆盘,如果圆盘的宽度大于井在某个高度的宽度,则圆盘被卡住(恰好等于的话会下去)。

盘子有几种命运:1、掉到井底。2、被卡住。3、落到别的盘子上方。

盘子的高度也是单位高度。给定井的宽度和每个盘子的宽度,求最终落到井内的盘子数量。

阅读全文 »

题目

一个单词a如果通过交换单词中字母的顺序可以得到另外的单词b,那么定义b是a的Anigram,例如单词army和mary互为Anigram。另:相同的2个单词不算Anigram。现在给定一个字典,输入Q个单词,从给出的字典中找出这些单词的Anigram。

思路

一个map保存原串出现次数,一个map保存排序串出现次数,减一下

阅读全文 »

题目

一整数数列a1, a2, ... , an(有正有负),以及另一个整数k,求一个区间[i, j],(1 <= i <= j <= n),使得a[i] + ... + a[j] = k。

思路

前缀和+n方查找,1e8如果数据小也是可以过的,毕竟1级题,也可以求前缀和,对前缀和排序,枚举i位置,二分查找j位置。

阅读全文 »

题意

有2堆石子。A B两个人轮流拿,A先拿。每次可以从一堆中取任意个或从2堆中取相同数量的石子,但不可不取。拿到最后1颗石子的人获胜。假设A B都非常聪明,拿石子的过程中不会出现失误。给出2堆石子的数量,问最后谁能赢得比赛。

阅读全文 »

题意

有一堆石子共有N个。A B两个人轮流拿,A先拿。每次只能拿1,3,4颗,拿到最后1颗石子的人获胜。假设A B都非常聪明,拿石子的过程中不会出现失误。给出N,问最后谁能赢得比赛。

思路

博弈论,从零开始推状态,方法详见欧几里得游戏(博弈论启蒙)。 最后发现%7为0和2的数是必败态,其它是必胜态。

阅读全文 »

题目描述

农历新年马上就要到了,奶牛们计划举办一次聚会庆祝新年的到来。但是,奶牛们并不喜欢走太远的路,这会给他们的聚会带来消极情绪,当一头奶牛的消极指数为Wi,他参加聚会所需行走的距离为si,那么他就会给聚会带来Si*Wi的消极情绪。所有奶牛所在位置都在一条直线上,已知所有奶牛的坐标和消极指数,求如何确定聚会地点,使得所有奶牛给聚会带来的消极情绪之和最小,输出消极情绪之和的最小值。

输入

第一行包含一个整数 Ca(Ca<=20) ,表示有 Ca 组测试数据。

对于每组测试数据:第一行包含一个整数n(1<=n<=50000) ,表示奶牛的数量。接下来 n 行每行包含两个浮点数Si和wi (-106<=Si<=106, 0<Wi<15)。

输出

对于每组测试数据,输出 "Case #c: ans" ,其中c表示测试数据编号,ans表示消极情绪之和的最小值,结果四舍五入为一个整数。

阅读全文 »