同余类和剩余系
全体正整数在模p后,按照余数分成的[0, p-1]的p个等价类,每个等价类就是一个模p同余类。全体同余类构成模p的剩余系 。在[1, p-1]中有
φ
(
p
)
\varphi(p)
φ(p)个于p互质的数,这些数所代表的同余类,构成p的简化同余类。简化同余类在模p乘法的代数系统中封闭成群。
数论欧拉定理
a
φ
(
m
)
≡
1
(
m
o
d
n
)
a^{\varphi(m)}\equiv1(mod\ n)
aφ(m)≡1(mod n)
当n为质数时,费马小定理
a
n
−
1
≡
1
(
m
o
d
n
)
a^{n-1} \equiv 1(mod\ n)
an−1≡1(mod n)
有推论(多用于降幂)
a
b
≡
a
b
m
o
d
φ
(
n
)
(
m
o
d
n
)
a^b\equiv a^{b\ mod\ \varphi (n)}(mod\ n)
ab≡ab mod φ(n)(mod n)
但是这个必须保证a,n互质。所有我们还有几个广义公式:
{
a
b
≡
a
b
m
o
d
φ
(
n
)
m
o
d
n
(
b
<
φ
(
n
)
)
a
b
≡
a
b
m
o
d
(
φ
(
n
)
)
+
φ
(
n
)
m
o
d
n
(
b
>
=
φ
(
n
)
\left\{ \begin{aligned} a^b \equiv &&a^{b\ mod\ \varphi(n)}mod\ n (b<\varphi(n))\\ a^b \equiv && a^{b\ mod\ (\varphi(n) )+ \varphi(n)}mod\ n (b>=\varphi(n)\\ \end{aligned} \right.
{ab≡ab≡ab mod φ(n)mod n(b<φ(n))ab mod (φ(n))+φ(n)mod n(b>=φ(n)
欧拉降幂模板题
求
a
a
a
a
.
.
m
o
d
p
a^{a^{a^{a..}}}mod\ p
aaaa..mod p 一共b个a。
/*
欧拉广义应用--降幂
*/
//19年某场网络赛, 模板已验证
const int N = 1e6+5;
ll prim[N], phi[N], vis[N];
ll a, b, m;
void init()
{
int cnt = 0;
memset(vis, 0, sizeof(vis));
memset(prim, 0, sizeof(prim));
memset(phi, 0, sizeof(phi));
phi[1] = 1;
for(int i=2; i<=N-2; i++)
{
if(!vis[i])
{
prim[cnt++]=i;
phi[i]=i-1;
}
for(int j=0; j<cnt&&i*prim[j]<=N-2; j++)
{
vis[i*prim[j]]=1;
if(i%prim[j]==0)
{
phi[i*prim[j]]=phi[i]*prim[j]; break;
}
else
phi[i*prim[j]]=phi[i]*(prim[j]-1);
}
}
}
ll _pow(ll a, ll n, ll mod);
ll solve(ll a, ll b, ll mod)
{ // b个a % mod的值
if (mod == 1) return 0; // 1停止
if (b == 1) return a % mod; //递归出口
ll d = solve(a, b-1, phi[mod]);
if (d < phi[mod] && d) return _pow(a, d, mod);
else return _pow(a, d + phi[mod], mod);
}
int main( )
{
init( );
int t;
scanf("%d",&t);
while(t--)
{
scanf("%lld%lld%lld",&a,&b,&m);
if(b==0)
{
printf("%lld\n",1%m);
continue;
}
printf("%lld\n",solve(a,b,m)%m);
}
return 0;
}
原根与阶
原根,是一个数学符号。设m是正整数,a是整数,若a模m的阶等于φ(m),则称a为模m的一个原根。
其中阶的定义是这样的:在gcd(a, m) == 1 的情况下,令
a
t
≡
1
m
o
d
m
a^t \equiv 1\ mod\ m
at≡1 mod m成立的最小t,称为a对模m的阶。
原根最重要的一个定义就是:如果g是m的一个原根则
g
,
g
2
,
g
3
.
.
.
g
φ
(
m
)
g, g^2, g^3...g^{\varphi(m)}
g,g2,g3...gφ(m)%m后是与m互质的数的一个排列。在m是质数时,这点现得尤为特殊:因为
φ
(
m
)
=
m
−
1
\varphi(m)=m-1
φ(m)=m−1,所有上述m-1个数构成m-1的排列。而事实上对于一个质数m,他一共会有
φ
(
m
−
1
)
\varphi(m-1)
φ(m−1)个原根。(一般数如果有原根,则是
φ
(
φ
(
m
)
)
\varphi(\varphi(m))
φ(φ(m))),模m有原根,当且仅当:m =
1
,
2
,
4
,
p
,
2
p
,
p
n
,
1, 2, 4, p,2p, p^n,
1,2,4,p,2p,pn,p是奇质数。
原根有一些性质,其中a对模m的阶整除
φ
(
m
)
\varphi(m)
φ(m)。
g
,
g
2
,
g
3
.
.
.
g
t
−
1
g, g^2, g^3...g^{t-1}
g,g2,g3...gt−1(t是阶)%m两两不同余,如果g是m的一个原根,则该序列%m与m互质,构成g%m的简化剩余系。另外,对于阶还有一个比较重要的定理:
a
x
≡
a
y
(
m
o
d
p
)
a^x\equiv a^y(mod\ p)
ax≡ay(mod p)等价
x
≡
y
(
m
o
d
t
)
x\equiv y(mod\ t)
x≡y(mod t)(t是a对模p的阶)
/*
暴力找原根, 枚举g确认
*/
int phi(int n);
int _pow(int x,int y,int mod);
int G(int m)
{
int PHI=phi(m);
for(int g=2;;g++)
{
bool fla=1;
if(_pow(g,PHI,m)!=1)continue;
for(int i=1;i<PHI;i++)
if(_pow(g,i,m)==1)
{
fla=0;
break;
}
if(fla)return g;
}
}
组合数取模求解
1、n,m很小(1e5),mod很大(1e9),mod为质数。
直接预处理阶乘。用组合数公式求出。
2、n,m很小(1e3),mod很大(1e9),需要求出区间中所有组合数。
递推式推定。
3、n、m很大(1e18),mod比较小(1e5),mod为质数
Lucas定理。
下面是模板代码:
ll C(ll n, ll m, ll mod)
{
if (m > n) return 0;
return f[n] * _pow(f[m], mod - 2, mod) % mod * _pow(f[n - m], mod-2, mod) % mod;
}
ll Lucas(ll n, ll m, ll p)
{
if (!m) return 1;
return C(n % p, m % p, p) * Lucas(n / p, m / p, p) % p;
}
void init(int p)
{
f[0] = 1;
for (int i = 1; i <= p; i++)
f[i] = f[i-1] * i % p;
}
int main()
{
int t; cin >> t;
while(t--)
{
int a, b, c; scanf("%d%d%d", &a, &b, &c);
init(c);
printf("%lld\n", Lucas(a+b, b, c) );
}
return 0;
}
3、n、m很大(1e18),mod比较小(1e5),mod不一定是质数
exLucas(扩展卢卡斯)
同余方程
线性同余不在阐述,以下是高阶同余得两种情况
第一类:
a
x
≡
b
(
m
o
d
p
)
a^x\equiv b(mod\ p)
ax≡b(mod p)
(a,p互质,求x)
BSGS算法。我们设x=t*i-j,这里有
t
=
p
t=\sqrt{p}
t=p向上取整。则
0
<
=
j
<
=
t
−
1
0<=j<=t-1
0<=j<=t−1,则原式为
a
t
i
≡
b
∗
a
j
(
m
o
d
p
)
a^{ti}\equiv b*a^j(mod\ p)
ati≡b∗aj(mod p)
枚举j记录结果,再枚举i([1, t ])看看有没有对应即可
O
(
q
)
O(\sqrt q)
O(q)。
(a,p不互质,求x)
EX-BSGS算法。不断约分gcd(a, p),直至a,p互质,然后BSGS。
下面是模板代码(附有扩展欧几里得求逆元):
map<ll , int> f;
ll exgcd(ll a, ll b, ll &x, ll &y)
{
if (b == 0) {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 b)
{
ll x, y; exgcd(a, b, x, y);
return (x % b + b) % b;
}
ll _pow(ll a, ll b, ll mod)
{
ll res = 1;
while(b)
{
if (b & 1) res =res * a % mod;
b >>= 1;
a = a * a % mod;
}
return res % mod;
}
ll bsgs(ll a, ll b, ll p)
{
f.clear();
ll m = ceil(sqrt(p));
b %= p;
for (int i = 1; i <= m; i++)
b = b * a % p, f[b] = i;
ll tmp = _pow(a, m, p);
b = 1;
for (int i = 1; i <= m; i++)
{
b = b * tmp % p;
if (f[b]) return (i * m - f[b] + p) % p;
}
return -1;
}
ll exbsgs(ll a, ll b, ll p)
{
if (b == 1 || p == 1) return 0;
ll x, y;
ll g = exgcd(a, p, x, y), k = 0, na = 1;
while(g > 1)
{
if (b % g != 0) return -1;
k++; b /= g; p /= g; na = na * (a / g) % p;
if (na == b) return k;
g = exgcd(a, p, x, y);
}
int f = bsgs(a, b * inv(na, p) % p, p);
if (f == -1) return -1;
return f + k;
}
int main()
{
ll a, b, p;
while(cin >> a >> p >> b, !( !a && !b && !p) )
{
a %= p; b %= p;
ll res = exbsgs(a, b, p);
if (res == -1) puts("No Solution");
else printf("%lld\n", res);
}
return 0;
}
第二类:
x
a
≡
b
(
m
o
d
p
)
x^a\equiv b(mod\ p)
xa≡b(mod p)
1.找到p的一个原根g
2.求出
g
r
≡
b
(
m
o
d
p
)
g^r\equiv b(mod\ p)
gr≡b(mod p)的一个解r。(第一类)
3.令
x
≡
g
y
(
m
o
d
p
)
x\equiv g^y(mod\ p)
x≡gy(mod p)带回原式求
g
a
y
≡
g
r
(
m
o
d
p
)
g^{ay}\equiv g^r(mod\ p)
gay≡gr(mod p)
4.求解
a
y
≡
r
(
m
o
d
t
)
ay\equiv r(mod\ t)
ay≡r(mod t)
5.把y代入2中式子得到x。
以上是p是质数的解法。(因为我学习的博客没说p的范围,而如果用这套算法,p必须有原根。博客的代码也是按照p是质数写的。。emmm,或许是我忽略了什么,但是网上我找不到其他我看的懂得博客了。)
下面是模板:
/*
没能找到题目验证,,所以没有重构
*/
const int N=201010;
unordered_map<int,int> hsh;
int prime[N],tot,T,phi,ans[N],num,a,b,p,g,x,y;
int read(){
int sum=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){sum=sum*10+ch-'0';ch=getchar();}
return sum*f;
}
void work(int x){
int tmp=x;
tot=0;
for(int i=2;i*i<=x;i++){
if(tmp%i==0){
prime[++tot]=i;
while(tmp%i==0)tmp/=i;
}
}
if(tmp>1)prime[++tot]=tmp;
}
int ksm(int x,int b){
int tmp=1;
while(b){
if(b&1)tmp=tmp*x%p;
b>>=1;
x=x*x%p;
}
return tmp;
}
int BSGS(int a,int b){
hsh.clear();
int block=sqrt(p)+1;
int tmp=b;
for(int i=0;i<block;i++,tmp=tmp*a%p)hsh[tmp]=i;
a=ksm(a,block);
tmp=1;
for(int i=0;i<=block;i++,tmp=tmp*a%p){
if(hsh.count(tmp)&&i*block-hsh[tmp]>=0)return i*block-hsh[tmp];
}
}
int exgcd(int &x,int &y,int a,int b){
if(b==0){
x=1;y=0;
return a;
}
int gcd=exgcd(x,y,b,a%b);
int z=x;
x=y;y=z-(a/b)*y;
return gcd;
}
signed main(){
T=read();
while(T--){
p=read(),a=read(),b=read();
b%=p;
phi=p-1; // p是质数
work(phi);
for(int i=1;i<=p;i++){
bool flag=false;
for(int j=1;j<=tot;j++)if(ksm(i,phi/prime[j])==1){flag=true;break;}
if(flag==false){g=i;break;}
}
int r=BSGS(g,b);
int gcd=exgcd(x,y,a,phi);
if(r%gcd!=0){printf("No Solution\n");continue;}
x=x*r/gcd;
int k=phi/gcd;
x=(x%k+k)%k;
num=0;
while(x<phi){ans[++num]=ksm(g,x),x+=k;}
sort(ans+1,ans+1+num);
for(int i=1;i<=num;i++)printf("%lld ",ans[i]);
printf("\n");
}
return 0;
}
卡特兰数
卡特兰递推式:
C
a
t
a
l
a
n
(
n
)
=
∑
i
=
0
n
−
1
C
a
t
a
l
a
n
(
i
)
C
a
t
a
l
a
n
(
n
−
i
−
1
)
Catalan(n) = \sum_{i=0}^{n-1}Catalan(i)Catalan(n-i-1)
Catalan(n)=i=0∑n−1Catalan(i)Catalan(n−i−1)
其中
C
a
t
a
l
a
n
(
0
)
=
1
,
C
a
t
a
l
a
n
(
1
)
=
1
Catalan(0)=1,Catalan(1)=1
Catalan(0)=1,Catalan(1)=1
记得有次做题,杀了一半的脑细胞推出了这个式子,然后不知道是卡特兰数。所有就懵了。
C
a
t
a
l
a
n
(
n
)
=
C
2
n
n
n
+
1
(1)
Catalan(n) = \frac{C_{2n}^{n}}{n+1}\tag1
Catalan(n)=n+1C2nn(1)
C
a
t
a
l
a
n
(
n
)
=
C
2
n
n
−
C
2
n
n
−
1
(2)
Catalan(n) =C_{2n}^{n}-C_{2n}^{n-1}\tag2
Catalan(n)=C2nn−C2nn−1(2)
应用:
1~n的顺序入栈:一共有出栈顺Catalan(n)*n!n!种。
凸多边形的三角形划分:一个凸的n边形,用直线连接他的两个顶点使之分成多个三角形,每条直线不能相交,问一共有多少种划分方案: Catalan(n-2);
有n+1个叶子的满二叉树的个数:Catalan(n)
在nn的格子中,只在下三角行走,每次横或竖走一格,有多少中走法: Catalan(n)
在圆上选择2n个点,将这些点成对连接起来使得所得到的n条线段不相交的方法数?: Catalan(n)