基本数论常识和一些模板算法

同余类和剩余系
全体正整数在模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) an11(mod n)
有推论(多用于降幂)
a b ≡ a b   m o d   φ ( n ) ( m o d   n ) a^b\equiv a^{b\ mod\ \varphi (n)}(mod\ n) abab 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. {ababab 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 at1 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)=m1,所有上述m-1个数构成m-1的排列。而事实上对于一个质数m,他一共会有 φ ( m − 1 ) \varphi(m-1) φ(m1)个原根。(一般数如果有原根,则是 φ ( φ ( m ) ) \varphi(\varphi(m)) φ(φ(m))),模m有原根,当且仅当:m = 1 , 2 , 4 , p , 2 p , p n , 1, 2, 4, p,2p, p^n, 124p2p,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...gt1(t是阶)%m两两不同余,如果g是m的一个原根,则该序列%m与m互质,构成g%m的简化剩余系。另外,对于阶还有一个比较重要的定理: a x ≡ a y ( m o d   p ) a^x\equiv a^y(mod\ p) axay(mod p)等价 x ≡ y ( m o d   t ) x\equiv y(mod\ t) xy(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) axb(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<=t1,则原式为 a t i ≡ b ∗ a j ( m o d   p ) a^{ti}\equiv b*a^j(mod\ p) atibaj(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) xab(mod p)

1.找到p的一个原根g
2.求出 g r ≡ b ( m o d   p ) g^r\equiv b(mod\ p) grb(mod p)的一个解r。(第一类)
3.令 x ≡ g y ( m o d   p ) x\equiv g^y(mod\ p) xgy(mod p)带回原式求 g a y ≡ g r ( m o d   p ) g^{ay}\equiv g^r(mod\ p) gaygr(mod p)
4.求解 a y ≡ r ( m o d   t ) ay\equiv r(mod\ t) ayr(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=0n1Catalan(i)Catalan(ni1)
其中 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)=C2nnC2nn1(2)

应用:
1~n的顺序入栈:一共有出栈顺Catalan(n)*n!n!种。
凸多边形的三角形划分:一个凸的n边形,用直线连接他的两个顶点使之分成多个三角形,每条直线不能相交,问一共有多少种划分方案: Catalan(n-2);
有n+1个叶子的满二叉树的个数:Catalan(n)
在n
n的格子中,只在下三角行走,每次横或竖走一格,有多少中走法: Catalan(n)
在圆上选择2n个点,将这些点成对连接起来使得所得到的n条线段不相交的方法数?: Catalan(n)

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值