数论(未完待续)

这里只贴板子,就不证明了,说实话,NOIP完全不需要你去掌握各种复杂的证明。只要会优化就行。

P3383 【模板】线性筛素数

#include <cstdio>
#include <cstring>

bool isPrime[100000010];
//isPrime[i] == 1表示:i是素数
int Prime[6000010], cnt = 0;
//Prime存质数

void GetPrime(int n)//筛到n
{
	memset(isPrime, 1, sizeof(isPrime));
	//以“每个数都是素数”为初始状态,逐个删去
	isPrime[1] = 0;//1不是素数
	
	for(int i = 2; i <= n; i++)
	{
		if(isPrime[i])//没筛掉 
			Prime[++cnt] = i; //i成为下一个素数
			
		for(int j = 1; j <= cnt && i*Prime[j] <= n/*不超上限*/; j++) 
		{
        	//从Prime[1],即最小质数2开始,逐个枚举已知的质数,并期望Prime[j]是(i*Prime[j])的最小质因数
            //当然,i肯定比Prime[j]大,因为Prime[j]是在i之前得出的
			isPrime[i*Prime[j]] = 0;
            
			if(i % Prime[j] == 0)//保证prime[j]是i的最小质因数
				break; //“最小质因数 × 最大因数(非自己) = 这个合数”
		}
	}
}

int main()
{
	int n, q;
	scanf("%d %d", &n, &q);
	GetPrime(n);
	while (q--)
	{
		int k;
		scanf("%d", &k);
		printf("%d\n", Prime[k]);
	}
	return 0;
}

快速幂

#include<bits/stdc++.h>
using namespace std;
long long b,a,p,k,ans=1,c;
int main()
{
    scanf("%d%d%d",&b,&p,&k);
    a=b;c=p;
    while(p>0)
    {
        if(p%2!=0)
            ans=ans*b%k;
        b=b*b%k;
        p=p>>1;   
    }
    ans %= k;
    printf("%d^%d mod %d=%d",a,c,k,ans);
    return 0;
}

 裴蜀定理

裴蜀定理得名于法国数学家艾蒂安·裴蜀,说明了对任何整数a、b和它们的最大公约数d,关于未知数x和y的线性丢番图方程(称为裴蜀等式): ax + by = m 有解当且仅当m是d的倍数。

#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>

using namespace std;

inline int gcd(int x, int y) {
    return y ? gcd(y, x%y) : x;
}

int n;

int main() {
    scanf("%d", &n);
    int ans = 0, tmp;
    for(int i=1; i<=n; i++) {
        scanf("%d", &tmp);
        if(tmp < 0) tmp = -tmp;
        ans = gcd(ans, tmp);
    }
    printf("%d", ans);
}

求逆元:

逆元的意义就是在%的运算中把除法转换为乘法

 

扩展欧几里得: 

#include<bits/stdc++.h>
using namespace std;
int exgcd(int a, int b, int &x, int &y){//返回gcd(a,b) 并求出解(引用带回),每次函数中值的改动要传回上一层函数
	if(b==0){
		x = 1, y = 0;
		return a;
	}
	int x1,y1,gcd;
	gcd = exgcd(b, a%b, x1, y1);
	x = y1, y = x1 - a/b*y1;
	return gcd; 
}
int main(){
	int n,a,b,x,y;
	cin>>n;
	while(n--){
		cin>>a>>b;
		exgcd(a,b,x,y);
		cout<<x<<" "<<y<<endl;
	}
	return 0;
}
/*
	因为
		gcd(a,b)=gcd(b,a%b)
	而
		bx′+(a%b)y′=gcd(b,a%b)
		
	等于
		bx′+(a−⌊a/b⌋∗b)y′=gcd(b,a%b)

	转化
		ay′+b(x′−⌊a/b⌋∗y′)=gcd(b,a%b)=gcd(a,b)
	
	故而
		x=y′,y=x′−⌊a/b⌋∗y′

*/

  ax+by=c

  x′=x0∗c/d,y′=y0∗c/d

应用: 求解一次同余方程 ax≡b(modm)则等价于求

ax=m∗(−y)+b//从%的定义出发

ax+my=b

有解条件为gcd(a,m)|b,然后用扩展欧几里得求解即可

特殊情况 当 b=1且 a与m互质时 则所求的x即为a的逆元

void Exgcd(ll a, ll b, ll &x, ll &y) {
    if (!b) x = 1, y = 0;
    else Exgcd(b, a % b, y, x), y -= a / b * x;
}
int main() {
    ll x, y;
    Exgcd (a, p, x, y);
    x = (x % p + p) % p;
    printf ("%d\n", x); //x是a在mod p下的逆元
}

 快速幂:配合费马小定理

若p为素数,a为正整数,且a、p互质。
则有a^(p−1)≡1(mod p)

a∗x≡1(mod p)

a∗x≡a^(p−1)(mod p)

x≡a^(p−2)(mod p)

所以我们可以用快速幂来算出 ap−2(modp)ap−2(modp)的值,这个数就是它的逆元了

 线性算法:

前置:

首先我们有一个,1^−1≡1(mod p)

然后设 p=k∗i+r,(1<r<i<p) 也就是 k 是 p/i的商,r 是余数 。

再将这个式子放到(mod p)意义下就会得到:

k∗i+r≡0(mod p)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 3000010;
int inv[N],n,p;
int main()
{
	scanf("%d%d",&n,&p);
	inv[1] = 1;
	puts("1");
	for(int i = 2;i <= n;i ++)
	{
		inv[i] = (ll)(p - p / i) * inv[p % i] % p;
		printf("%d\n",inv[i]);
	}
	return 0;
}

PS:(-p/i+p)防止负数

线性求组合数

这里直接贴卢卡斯定理的例题,题目在洛谷P3807

#include<bits/stdc++.h>
#define mod 1000000007
using namespace std;
typedef long long LL;
const int maxn=2e5+7;
LL a[maxn],b[maxn];
LL Lucas(LL n,LL m,LL p)
{
    if(n<m) return 0;
    else if(n<p) return (b[n]*a[m]%p)*a[n-m]%p;
    else return Lucas(n/p,m/p,p)*Lucas(n%p,m%p,p)%p;
}
int main()
{
     LL n,m,p,t;
     scanf("%lld",&t);
     while(t--)
     {
          scanf("%lld%lld%lld",&n,&m,&p);
          a[0]=a[1]=b[0]=b[1]=1;
          for(int i=2;i<=n+m;i++) b[i]=b[i-1]*i%p;
          for(int i=2;i<=n+m;i++) a[i]=(p-p/i)*a[p%i]%p;
          for(int i=2;i<=n+m;i++) a[i]=a[i-1]*a[i]%p;
          printf("%lld\n",Lucas(n+m,m,p));
     }
}

欧拉函数

例题见洛谷P5091

#include <cstdio>
#include <ctype.h>
#include <cstring>
#include <algorithm>
#define ll long long
int a, b, m, phi, ans = 1;
bool flag;
inline int read() {
  int f = 1, w = 0; char ch = getchar();
  for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) {
    w = (w << 3) + (w << 1) + (ch ^ '0');
    if (w >= phi) {
      flag = true;
      w %= phi;
    }
  }
  return f * w;
}
void GetPhi() {
  int tmp = m; 
  phi = m;
  for (int i = 2; i * i <= m; ++ i) {
    if (! (tmp % i)) {
      phi = phi - phi / i;
      while (! (tmp % i)) tmp /= i;
    }
  }
  if (tmp > 1) phi = phi - phi / tmp;
}
int QuickPow(int x, int y) {
  int ret = 1;
  while (y) {
    if (y & 1) ret = 1ll * ret * x % m;
    x = 1ll * x * x % m, y >>= 1;
  }
  return ret;
}
int main() {
  scanf("%d%d", &a, &m);
  GetPhi();
  b = read();
  if (flag) b += phi;
  printf("%d", QuickPow(a, b));
  return 0;
}

扩展中国剩余定理

例题洛谷P4777 题解区第一篇有很好的解释

#include<bits/stdc++.h>
using namespace std;
#define LL long long 
int n;
LL m[100005],c[100005];
LL gcd(LL a,LL b)
{
    return !b?a:gcd(b,a%b);
}
LL exgcd(LL a, LL b, LL &x, LL &y){
	if(b==0){
		x = 1, y = 0;
		return a;
	}
	LL x1,y1,gcd;
	gcd = exgcd(b, a%b, x1, y1);
	x = y1, y = x1 - a/b*y1;
	return gcd; 
}

LL getInv(LL a,LL mod)
{
    LL x,y;
    LL d=exgcd(a,mod,x,y);
    return d==1?(x+mod)%mod:-1;
}
LL exCRT(int n)
{
    LL m1,m2,c1,c2,d;
    for(int i=2;i<=n;i++)
    {
        m1=m[i-1],m2=m[i],c1=c[i-1],c2=c[i];
        d=gcd(m1,m2);
        if((c2-c1)%d)
        {
            printf("%lld\n",c2-c1);
            return -1;
        }
        m[i] = m[i-1] * m[i] / d;
        c[i] = (c2-c1)/d * getInv(m1/d,m2/d) % ( m2 / d ) * m1 + c1;
        c[i] = ( c[i] % m[i] + m[i] ) % m[i];
    }
    return c[n];
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;++i)
        scanf("%d%d",&m[i],&c[i]);
    printf("%lld",exCRT(n));
    return 0;
}

例题:

1.【模板】快速幂||取余运算 - 洛谷

2.【模板】线性筛素数 - 洛谷

3.【模板】裴蜀定理 - 洛谷

4.【模板】乘法逆元 - 洛谷

5.信封问题 - 洛谷

6.青蛙的约会 - 洛谷

7. [JSOI2011]分特产 - 洛谷

 1,2,3,4见上

5. 信封问题

繁难则简:可以得出 D[1]=0,D[2]=1, D[3]=2。

 在计算D[n]中,我们先把n提出来,那么1 ~ n - 1中必然有一个k。

分两种情况:

1.k在n的位置上(满足错排)即D[n - 1]

2.k不在n的位置上(为了满足错排),即D[n - 2].

因为k一共有n - 1种选法

故D[n] = (D[n - 1] + D[n - 2]) * (n - 1),递推求解即可。

6.青蛙的约会

不能简单的认为这是一个追及问题,因为两个青蛙的位移是跳跃性的,不是连续性的,一只青蛙可能从另一只青蛙上“跳”过去,经过而不相遇。

所以我们写下方程式

x+k*m≡y+k*n(modl)

移项:

k*  (m−n)−l * z = −(x−y)

令 S = x−y,W = n−m

k * W+l * z = S 

用扩展欧几里得:

kj * ​W+l * zj​=(W,l)

()表示gcd

当gcd(w,l)|S有解

之后,这个方程的所有解就可以表示成 

ki​=kj​+t*L / gcd(W,l)

 而因为这个k是建立在exgcd得出的方程上的,方程右边是gcd(W,l)而不是S,所以最后我们需要将结果 * S / gcd(W,L)

7.分特产

 详见[JSOI2011]分特产 - 洛谷

posted @ 2022-09-24 21:16  zyc_xianyu  阅读(17)  评论(0编辑  收藏  举报