数论

数论

一、质数

1)试除法判断质数

bool isPrime(int x)
{
    if(x < 2) return false;
    for(int i = 2;i<=x/i;i++)
        if(x%i==0) return false;
    return true;
}

2)分解质因数

void divide(int n)
{
    for (int i = 2; i <= n / i; i ++ )
    {
        if( n % i == 0 )
        {
            int s = 0;
            while (n % i == 0)
            {
                n /= i;
                s++;
            }
            printf("%d %d\n",i,s);
        }
    }
    if (n > 1) printf("%d %d\n",n,1);
    puts("");
}

3)筛质数

1、普通筛质数

void getPrimes(int n)
{
    for (int i = 2; i <= n; i ++)
    {
        if (!st[i])
        {
            primes[cnt ++] = i;
        }
        for (int j = i + i; j <= n; j ++) st[j] = true;
    }
}

2、埃氏筛质数

void getPrimes(int n)
{
    for (int i = 2; i <= n; i ++)
    {
        if (!st[i])
        {
            primes[cnt ++] = i;
            for (int j = i + i; j <= n; j ++) st[j] = true;  //只对质数的倍数筛掉
        }
    }
}

3、欧拉筛

void getPrimes(int n)
{
    for (int i = 2; i <= n; i ++)
    {
        if(!st[i]) primes[cnt ++] = i;
        for (int j = 0; primes[j] <= n / i; j ++)
        {
         	st[primes[j] * i] = true;  //每次把对应的倍数筛掉
            if (i % primes[j] == 0) break;  //primes[j]是i的最小质因子,我们只用最小质因子来筛,所以遇到了就break
        }
    }
}

二、约数

1)试除法求约数

#include <bits/stdc++.h>
using namespace std;
int n;
vector<int> gcd(int n)
{
    vector<int> res;
    for(int i = 1; i <= n / i; i ++)
    {
        if(n % i == 0)
        {
            res.push_back(i);
            if (i != n / i) res.push_back(n / i);
        }
    }
    sort(res.begin(),res.end());
    return res;
}

int main()
{
    scanf("%d", &n);
    while (n --)
    {
        int x;
        scanf("%d", &x);
        auto res = gcd(x);
        for(auto item:res) printf("%d ", item);
        puts("");
    }
    return 0;
}

2)求n个数的积对常数取模的结果

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e9+7;
int n;
int main()
{
    cin >> n;
    ll res = 1;
    unordered_map<int,int> primes;
    while (n --)
    {
        int x;
        cin >> x;
        for (int i = 2; i <= x / i; i ++)
        {
            while (x % i == 0)
            {
                x /= i;
                primes[i] ++ ;
            }
        }
        if(x > 1) primes[x] ++ ;
    }
    //至此,我们就把最后的乘积的每个质因子的指数都求出来了,根据公式得出约数个数是(a1+1)*(a2+1)……*(ak+1);
    for (auto item : primes) res = res * (item.second + 1) % N;
    printf("%lld", res);
    return 0;
}

3)求n个数的积的约数个数

#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const int mod = 1e9 +7;
int n;
int main()
{
    cin>>n;
    unordered_map<int,int> primes;
    while (n -- )
    {
        int x;
        cin>>x;
        for(int i = 2;i<=x/i;i++)
            while(x%i==0)
            {
                x/=i;
                primes[i]++;
            }
        if(x>1) primes[x]++;
    }
    ll res = 1;
    for(auto prime:primes)
    {
        int p = prime.first, a = prime.second;
        ll t = 1;
        while (a -- ) t = (t * p + 1) % mod;
        res = res * t % mod;
    }
    cout<<res;
    return 0;
}

4)求最大公约数

int gcd(int a, int b)
{
    return b ? gcd(b, a % b) : a;
}

三、欧拉函数

欧拉函数的证明

1)欧拉函数

#include <iostream>
#include <algorithm>
using namespace std;
int main()
{
    int n;
    cin >> n;
    while (n --)
    {
        int a;
        cin >> a;
        int res = a; // res存储最终的结果
        for (int i = 2; i <= a / i; i ++) // 对数据先进行质因数分解
        {
            if(a % i == 0) // i是a的一个约数
            {
                res = res / i * (i - 1); // 化简之后的,相当于乘以 1-(1/i) 
                while (a % i == 0) a /= i;
            }
        }
        if(a > 1) res = res / a * (a - 1);
        cout << res <<endl;
    }
    return 0;
}

2)筛法求欧拉函数

#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N = 1e6+5;
int primes[N],cnt;
ll phi[N]; // 欧拉函数
bool st[N]; // 标记是否是质数
ll get_Olers(int n)
{
    phi[1] = 1;
    for (int i = 2; i <= n; i ++ )
    {
        if(!st[i])
        {
            primes[cnt ++ ] = i;
            phi[i] = i-1; // i是质数,1~i-1都与它互质
        }
        for(int j = 0; primes[j] <= n / i; j ++)
        {
            st[primes[j] * i] = true;
            if(i % primes[j] == 0)
            {
                phi[primes[j] * i] = phi[i] * primes[j];
                break;
            }
  //如果primes[j]不是i的质因子,那么phi[primes[j]*i] = phi[i] * primes[j] * (1-1/primes[j]),化简得到如下代码
            phi[primes[j] * i] = phi[i] * (primes[j] - 1);
        }
    }
    ll res = 0;
    for(int i = 1; i <= n; i ++) res += phi[i];
    return res;
}
int main()
{
    int n;
    cin >> n;
    cout << get_Olers(n) << endl;
    return 0;
}

四、快速幂

欧拉定理

快速幂

#include <iostream>
using namespace std;
typedef long long ll;
ll qmi(ll a, ll k, ll p)
{
    ll res = 1;
    while (k)
    {
        if (k & 1) res = res * a % p;
        k = k >> 1;
        a = a * a % p;
    }
    return res;
}
int main()
{
    int n;
    cin >> n;
    while (n -- )
    {
        ll a,k,p;
        scanf("%lld%lld%lld",&a,&k,&p);
        printf("%lld\n",qmi(a,k,p));
    }
    return 0;
}

快速幂求逆元

#include <iostream>
using namespace std;
typedef long long ll;
ll qmi(ll a, ll k, ll p)
{
    ll res = 1;
    while (k)
    {
        if (k & 1) res = res * a % p;
        k = k >> 1;
        a = a * a % p;
    }
    return res;
}
int main()
{
    int n;
    cin >> n;
    while (n -- )
    {
        ll a,p;
        scanf("%lld%lld",&a,&p);
        ll res = qmi(a,p-2,p);
        if (a % p) printf("%lld\n",res);
        else puts("impossible");
    }
    return 0;
}

五、扩展欧几里得算法

裴属定理

扩展欧几里得算法(求ax + by = gcd(a,b) 中的x和y的算法)

#include <iostream>
using namespace std;
int exgcd(int a, int b, int &x, int &y)
{
    if(!b)
    {
        x = 1,y = 0;
        return a;
    }
    int d = exgcd(b, a % b, y, x);
    y -= a / b * x;
    return d;
}
int main()
{
    int n;
    cin >> n;
    while (n -- )
    {
        int a,b,x,y;
        scanf("%d%d",&a, &b);
        exgcd(a,b,x,y);
        printf("%d %d\n",x,y);
    }
    return 0;
}

线性同余方程(求 ax % m = b 式子中的x,给定a,b,m)

#include <iostream>
using namespace std;
typedef long long ll;
int exgcd(int a,int b,int &x,int &y)
{
    if(!b)
    {
        x = 1, y = 0;
        return a;
    }
    
    int d = exgcd(b,a%b,y,x);
    y -= a/b*x;
    return d;
}
int main()
{
    int n;
    cin >> n;
    while (n -- )
    {
        int a,b,m;
        scanf("%d%d%d",&a,&b,&m);
        int x,y;
        int d = exgcd(a,m,x,y);
        if (b % d) puts("impossible");
        else printf("%d\n",(ll)x * b / d % m);
    }
    return 0;
}

六、中国剩余定理

F72009F791B0CF9C9CC369CEAB2A2B85.jpg

题目:

给定 2n2𝑛 个整数 a1,a2,…,an𝑎1,𝑎2,…,𝑎𝑛 和 m1,m2,…,mn𝑚1,𝑚2,…,𝑚𝑛,求一个最小的非负整数 x𝑥,满足 ∀i∈[1,n],x≡mi(mod ai)∀𝑖∈[1,𝑛],𝑥≡𝑚𝑖(𝑚𝑜𝑑 𝑎𝑖)。

输入格式

第 11 行包含整数 n𝑛。

第 2…n+12…𝑛+1 行:每 i+1𝑖+1 行包含两个整数 ai𝑎𝑖 和 mi𝑚𝑖,数之间用空格隔开。

输出格式

输出最小非负整数 x𝑥,如果 x𝑥 不存在,则输出 −1−1。

数据范围

1≤ai≤231−11≤𝑎𝑖≤231−1,
0≤mi<ai0≤𝑚𝑖<𝑎𝑖
1≤n≤251≤𝑛≤25
所有 mi𝑚𝑖 的最小公倍数在 6464 位有符号整数范围内。

输入样例:

2
8 7
11 9

输出样例:

31
#include<iostream>
using namespace std;
typedef long long LL;//数据范围比较大,所以用LL来存储
LL exgcd(LL a,LL b,LL &x,LL &y)
{
    if(!b)
    {
        x=1,y=0;
        return a;
    }
    LL d=exgcd(b,a%b,y,x);
    y-=a/b*x;
    return d;
}
int main()
{
    int n;
    LL a1,m1;
    cin>>n>>a1>>m1;
    LL x=0;
    for(int i=1;i<n;i++)
    {
        LL a2,m2;
        cin>>a2>>m2;
        LL k1,k2;
        LL d=exgcd(a1,a2,k1,k2);
        if((m2-m1)%d)
        {
            x=-1;
            break;
        }
        k1*=(m2-m1)/d;
        //因为此时k1是k1*a1+k2*a2=d的解,所以要乘上(m2-m1)/d的倍数大小
        LL t=abs(a2/d);
        k1=(k1%t+t)%t;
        //数据比较极端,所以只求k的最小正整数解
        m1=k1*a1+m1;
        //m1在被赋值之后的值为当前"x"的值,此时赋值是为了方便下一轮的继续使用
        a1=abs(a1*a2/d);
        //循环结束时a1的值为当前所有的a1,a2,……an中的最小公倍数
    }
    if(x!=-1)
    x=(m1%a1+a1)%a1;
    //当循环结束时,此时的值应该与最小公倍数取模,以求得最小正整数解
    printf("%lld\n",x);
    return 0;
}

七、高斯消元法解方程

1)高斯消元法解线性方程组

#include <iostream>
#include <cmath>
using namespace std;
const int N = 110;
const double eps = 1e-6;
int n;
double a[N][N];
int gauss()  // 高斯消元,答案存于a[i][n]中,0 <= i < n
{
    int c, r;
    for (c = 0, r = 0; c < n; c ++ )
    {
        int t = r;
        for (int i = r; i < n; i ++ )  // 找绝对值最大的行
            if (fabs(a[i][c]) > fabs(a[t][c]))
                t = i;
        if (fabs(a[t][c]) < eps) continue;
        for (int i = c; i <= n; i ++ ) swap(a[t][i], a[r][i]);  // 将绝对值最大的行换到最顶端
        for (int i = n; i >= c; i -- ) a[r][i] /= a[r][c];  // 将当前行的首位变成1
        for (int i = r + 1; i < n; i ++ )  // 用当前行将下面所有的列消成0
            if (fabs(a[i][c]) > eps)
                for (int j = n; j >= c; j -- )
                    a[i][j] -= a[r][j] * a[i][c];
        r ++ ;
    }
    if (r < n)
    {
        for (int i = r; i < n; i ++ )
            if (fabs(a[i][n]) > eps)
                return 2; // 无解
        return 1; // 有无穷多组解
    }
    for (int i = n - 1; i >= 0; i -- )
        for (int j = i + 1; j < n; j ++ )
            a[i][n] -= a[i][j] * a[j][n];
    return 0; // 有唯一解
}
int main()
{
    cin >> n;
    for(int i = 0; i < n; i ++ )
        for(int j = 0; j < n+1; j ++ )
            cin >> a[i][j];
    int t = gauss();
    if (!t)
    {
        for (int i = 0; i < n; i ++ ) printf("%.2lf\n", a[i][n]);
    }else if (t == 1) printf("Infinite group solutions");
    else printf("No solution");
    return 0;
}

2)高斯消元解异或线性方程组

#include <iostream>
#include <algorithm>

using namespace std;

const int N = 110;

int n;
int a[N][N];
int gauss()
{
    int c,r;
    for(c=0,r=0;c<n;c++)
    {
        // 找主元
        int t=-1;
        for(int i=r;i<n;i++)
            if(a[i][c])
            {
                t=i;
                break;
            }
        if(t==-1) continue;
        // 交换主元行
        for(int j=c;j<=n;j++) swap(a[r][j],a[t][j]);
        // 左下角消
        for(int i=r+1;i<n;i++)
            if(a[i][c])//漏了
                for(int j=n;j>=c;j--)//漏了
                    a[i][j] ^= a[r][j];
        r++;
    }
    // 判断
    if(r<n)
    {
        for(int i=r;i<n;i++)//i=r
            if(a[i][n])
                return 2;
        return 1;
    }
    // 消右上角
    for(int i=n-1;i>=0;i--)
        for(int j=i+1;j<n;j++)
        //如果是0 就不用下面的a[j][j] 来^a[i][j]了
        //如果不是0 才需要用第j行第j列a[j][j]来^第i行第j列a[i][j] 
        //进而进行整行row[i]^row[j] 间接导致 a[i][n]^a[j][n]
            if(a[i][j])
                a[i][n]^=a[j][n];
    return 0;
}
int main()
{
    cin >> n;
    for(int i=0;i<n;i++)
        for(int j=0;j<=n;j++)
            cin >> a[i][j];
    int t = gauss();
    if(t==0)
    {
        for(int i=0;i<n;i++) cout << a[i][n] << endl;
    }
    else if(t==1) puts("Multiple sets of solutions");
    else puts("No solution");
    return 0;
}

八、求组合数

1)求组合数Ⅰ

#include <iostream>
using namespace std;
const int N = 2001, mod = 1e9+7;
int c[N][N];
void init()
{
    for(int i = 0; i < N; i ++ )
        for(int j = 0; j <= i; j ++ )
            if (!j) c[i][j] = 1;
            else c[i][j] = (c[i-1][j-1] + c[i-1][j]) % mod;
}
int main()
{
    init();
    int n;cin >> n;
    while (n -- )
    {
        int a, b;
        cin >> a >> b;
        cout << c[a][b] <<endl;
    }
    return 0;
}

2)求组合数 II

当N的数量级超过几千的时候,就不能根据递推式一次性处理完了,用组合数的定义来处理)

#include <iostream>
using namespace std;
typedef long long ll;
const int N = 1000010, mod = 1e9+7;
ll fact[N], infact[N];
ll qmi(int a, int k , int p)
{
    ll res = 1;
    while (k)
    {
        if (k & 1) res = res * a % p;
        k >>= 1;
        a = (ll)a * a % p;
    }
    return res;
}
int main()
{
    fact[0] = infact[0] = 1;
    for (int i = 1; i < N; i ++ )
    {
        fact[i] = fact[i-1] * i % mod;
        infact[i] = infact[i-1] * qmi(i, mod-2, mod) % mod; // qmi(i, mod - 2, mod) 是i模mod的逆元,即1/i (% mod)
    }
    int n;cin >> n;
    while (n -- )
    {
        int a, b;cin >> a >> b;
        cout << fact[a] * infact[b] % mod * infact[a-b] % mod << endl;
    }
    return 0;
}

3)求组合数Ⅲ

N的数量级非常大的时候,用卢卡斯定理: C[a][b] = C[a%p][b%p] * C[a/p][b/p]; p是对结果取模的数

#include <iostream>
using namespace std;
typedef long long ll;
int p;
int qmi(int a,int k) // p是全局变量,就不作形参了
{
    int res = 1;
    while (k)
    {
        if (k & 1) res = (ll) res * a % p;
        a = (ll)a * a % p;
        k >>= 1;
    }
    return res;
}
int C(int a, int b)
{
    int res = 1;
    for (int i = 1, j = a; i <= b; i ++, j -- )
    {
        res = (ll)res * j % p;
        res = (ll)res * qmi(i, p-2) % p;
    }
    return res;
}
ll lucas(ll a, ll b)
{
    if (a < p && b < p) return C(a, b) % p;
    return (ll)C(a % p, b % p) * lucas(a / p, b / p) % p;
}
int main()
{
    int n;
    cin >> n;
    while (n -- )
    {
        ll a ,b;
        cin >> a >> b >> p;
        cout << lucas(a, b) << endl;
    }
    return 0;
}

4)求组合数Ⅳ

用高精度

#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
const int N=5010;
int primes[N],cnt;
int sum[N];
bool st[N];
void get_primes(int n)
{
    for(int i=2;i<=n;i++)
    {
        if(!st[i])primes[cnt++]=i;
        for(int j=0;primes[j]*i<=n;j++)
        {
            st[primes[j]*i]=true;
            if(i%primes[j]==0)break;//==0每次漏
        }
    }
}
// 对p的各个<=a的次数算整除下取整倍数
int get(int n,int p)
{
    int res =0;
    while(n)
    {
        res+=n/p;
        n/=p;
    }
    return res;
}
//高精度乘
vector<int> mul(vector<int> a, int b)
{
    vector<int> c;
    int t = 0;
    for (int i = 0; i < a.size(); i ++ )
    {
        t += a[i] * b;
        c.push_back(t % 10);
        t /= 10;
    }
    while (t)
    {
        c.push_back(t % 10);
        t /= 10;
    }
    // while(C.size()>1 && C.back()==0) C.pop_back();//考虑b==0时才有pop多余的0 b!=0不需要这行
    return c;
}
int main()
{
    int a,b;
    cin >> a >> b;
    get_primes(a);

    for(int i=0;i<cnt;i++)
    {
        int p = primes[i];
        sum[i] = get(a,p)-get(a-b,p)-get(b,p);//是a-b不是b-a
    }

    vector<int> res;
    res.push_back(1);

    for (int i = 0; i < cnt; i ++ )
        for (int j = 0; j < sum[i]; j ++ )//primes[i]的次数
            res = mul(res, primes[i]);

    for (int i = res.size() - 1; i >= 0; i -- ) printf("%d", res[i]);
    puts("");

    return 0;
}

5)卡特兰数

\(C_{2n}^{n} - C_{2n}^{n-1} = \frac{C_{2n}^{n}}{n+1}\)

提供了两个板子,但是第二个似乎更好用一些

#include <iostream>
using namespace std;
typedef long long ll;
const int N = 200010, mod = 1e9+7;
ll fact[N], infact[N];
ll qmi(int a, int k , int p)
{
    ll res = 1;
    while (k)
    {
        if (k & 1) res = res * a % p;
        k >>= 1;
        a = (ll)a * a % p;
    }
    return res;
}
int main()
{
    fact[0] = infact[0] = 1;
    for (int i = 1; i < N; i ++ )
    {
        fact[i] = fact[i-1] * i % mod;
        infact[i] = infact[i-1] * qmi(i, mod-2, mod) % mod; // qmi(i, mod - 2, mod) 是i模mod的逆元,即1/i (% mod)
    }
    int n;
    cin >> n;
    cout << fact[2*n] * infact[n] % mod * infact[n] % mod * qmi(n+1, mod -2, mod) % mod<< endl;
    return 0;
}
#include <iostream>
using namespace std;
typedef long long ll;
const int mod = 1e9+7;
int qmi(int a, int k, int p)
{
    ll res = 1;
    while (k)
    {
        if (k & 1) res = (ll)res * a % p;
        k >>= 1;
        a = (ll)a * a %p;
    }
    return res;
}
int main()
{
    int n;
    cin >> n;
    int a = 2 * n, b = n;
    int res = 1;
    for (int i = a; i > a-b; i --)
    {
        res = (ll)res * i % mod;
    }
    for (int j = 1; j <= b; j ++){
        res = (ll)res * qmi(j, mod-2, mod) % mod;
    }
    cout << (ll)res * qmi(n+1, mod - 2, mod) % mod << endl;
    return 0;
}

九、容斥原理

题目:

给定一个整数 n𝑛 和 m𝑚 个不同的质数 p1,p2,…,pm𝑝1,𝑝2,…,𝑝𝑚。

请你求出 1∼n1∼𝑛 中能被 p1,p2,…,pm𝑝1,𝑝2,…,𝑝𝑚 中的至少一个数整除的整数有多少个。

输入格式

第一行包含整数 n𝑛 和 m𝑚。

第二行包含 m𝑚 个质数。

输出格式

输出一个整数,表示满足条件的整数的个数。

#include <iostream>
#include <algorithm>
using namespace std;
typedef long long LL;
const int N = 20;
int p[N];
int main()
{
    int n, m;
    cin >> n >> m;
    // 用p数组存储m个质数
    for (int i = 0; i < m; i ++ ) cin >> p[i];
    int res = 0;
    // --------------------1、每一个i代表一种可能的取法,最外层的循环遍历置2的m次方后,可以取完所有的取法--------------------
    // ① 为什么是2的m次方?最外层循环的作用是什么?
    // 从1开始枚举,枚举到1 << m(左移m位。左移一位相当于乘2,右移一位相当于除2),即2的m次方;
    for (int i = 1; i < 1 << m ; i ++ )
    {
        // t代表当前所有质数的乘积,s代表什么当前选法包含几个集合
        int t = 1, s = 0;
        // --------------------2、这个循环就是提取出这个i值对应的取法--------------------
        // 枚举m个质数,依次计算容斥原理的公式
        for (int j = 0; j < m; j ++ ){
            // i右移j位与上1,即如果当前位是1的话
            // ② i在这里为什么要用位运算?
            if (i >> j & 1)
            {
                // (LL)t * p[j] > n
                // 如果t(已有的质数选法)乘上这个质数大于给定的数n,说明1∼n中的数不能被p整除
                // 此时直接返回break,跳过这个质数
                if ((LL)t * p[j] > n)
                {
                    t = -1;
                    break; // break的作用域是跳出整个循环
                }
                // 将该质数乘到t中
                t *= p[j];
                // s表示当前选法中有多少个集合
                s ++ ;
            }
        }
        // --------------------3、再将提取出的取法代入公式--------------------
        // 如果t不等于-1(-1是给定的flag值)
        if (t != -1)
        {
            // ③ s为什么要模2?
            if (s % 2) res += n / t;
            else res -= n / t;
        }
    }
    cout << res << endl;
    return 0;
}

十、博弈论

1、Nim游戏

先把所有的数异或得到一个结果:

1、结果为0:作为先手,那么不管取出多少物品,之后所有的数再次异或得到的结果就肯定不再为0

2、结果为x(x != 0,且x的最高位为1是第k位):那么在a1~an中肯定存在一个ai的第k位为1,作为先手,只需要取出ai-(ai ^ x)个物品,就可以使得下次异或的结果为0,让后手必输,那么先手必胜!

于是得到结果:最开始的时候,所有值异或的结果不为0就先手必胜,否则先手必败!

#include <iostream>
using namespace std;
int n;
int main()
{
    scanf("%d", &n);
    int res = 0;
    while (n -- )
    {
        int x;
        scanf("%d", &x);
        res ^= x;
    }
    if (res) puts("Yes");
    else puts("No");
    return 0;
}

2、集合-Nim游戏

#include <iostream>
#include <cstring>
#include <unordered_set>
using namespace std;
const int N = 110, M = 100010;
int s[N], f[M];
int n,m;
int sg(int x)
{
    if (f[x] != -1) return f[x];
    unordered_set<int> S;
    for(int i = 0; i < m; i ++ )
    {
        int sum = s[i];
        if (x >= sum) S.insert(sg(x-sum));
    }
    for(int i = 0; ; i ++ )
        if (!S.count(i))
            return f[x] = i;
}
int main()
{
    cin >> m;
    for (int i = 0; i < m; i ++ ) cin >> s[i];
    cin >> n;
    memset(f, -1, sizeof f);
    int res = 0;
    for (int i = 0; i < n; i ++ )
    {
        int x;
        cin >> x;
        res ^= sg(x);
    }
    if (res) puts("Yes");
    else puts("No");
    return 0;
}

十一、莫比乌斯反演



!!!注意:代码中的qpow()是指快速幂函数

posted @ 2024-11-24 18:43  来杯whiskey  阅读(21)  评论(0编辑  收藏  举报