acwing提高课数论

矩阵快速幂

通过构造一个 3 ⋅ 3 3·3 33的矩阵来快速求得大数斐波那契和。
定义 F n = ( f n , f n + 1 , S n ) F_n=(f_n,f_{n+1},S_n) Fn=(fn,fn+1,Sn)分别代表斐波那契数和前n项的和

构 造 A = [ 0 1 0 1 1 1 0 0 1 ] 构造A=\begin{bmatrix} 0 & 1&0 \\ 1 & 1&1 \\0&0&1 \end{bmatrix} A=010110011

可使得 F n + 1 = F n ⋅ A F_{n+1}=F_n·A Fn+1=FnA,所以 F n = F 1 ⋅ A n − 1 F_n=F_1·A^{n-1} Fn=F1An1,A的次方可以用快速幂来实现。

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

typedef long long LL;

const int N = 3;

int n, m;

void mul(int c[], int a[], int b[][N])//形成新的Fn矩阵
{
    int temp[N] = {0};
    for (int i = 0; i < N; i ++ )
        for (int j = 0; j < N; j ++ )
            temp[i] = (temp[i] + (LL)a[j] * b[j][i]) % m;

    memcpy(c, temp, sizeof temp);//矩阵做参数可以自动传回
}

void mul(int c[][N], int a[][N], int b[][N])//重塑a矩阵
{
    int temp[N][N] = {0};
    for (int i = 0; i < N; i ++ )
        for (int j = 0; j < N; j ++ )
            for (int k = 0; k < N; k ++ )
                temp[i][j] = (temp[i][j] + (LL)a[i][k] * b[k][j]) % m;

    memcpy(c, temp, sizeof temp);
}

int main()
{
    cin >> n >> m;

    int f1[N] = {1, 1, 1};
    int a[N][N] = {
        {0, 1, 0},
        {1, 1, 1},
        {0, 0, 1}
    };

    n -- ;//计算A的n-1次方
    while (n)
    {
        if (n & 1) mul(f1, f1, a);  // res = res * a
        mul(a, a, a);  // a = a * a
        n >>= 1;
    }

    cout << f1[2] << endl;

    return 0;
}

阶乘分解

题意:给定整数 N N N,把阶乘 N N N 分解质因数,按照算术基本定理的形式输出分解结果中的 质因子 和 对应次数。 1 ≤ N ≤ 1 0 6 1≤N≤10^6 1N106.
思路: 这道题暴力的话是枚举1~N的每一个数,对每一个数分解质因数,然后求和,时间复杂度大概是 n ⋅ n n·\sqrt{n} nn ,显然超时。
首先 1 1 1—— n n n 中所有质数的个数大概是 n l o g n \dfrac{n}{log_n} lognn,这个题大概是 50000 50000 50000左右。由算数基本定理可知,在 1 1 1—— n 连 乘 n连乘 n 中对于每一个质数 p i p_i pi p p p 的次数是 n p + n p 2 + n p 3 + . . . . n p m ( p m < = n ) \dfrac{n}{p}+\dfrac{n}{p^2}+\dfrac{n}{p^3}+....\dfrac{n}{p^m}(p^m<=n) pn+p2n+p3n+....pmn(pm<=n)
因为对于任意的 C C C· p k p^k pk,k总能被最小因子的次方累加。比如 10 ! 10! 10中求2的次数。
在这里插入图片描述

10 2 1 \dfrac{10}{2^1} 2110 = 5 =5 =5 2 1 2^1 21的贡献, 10 2 2 \dfrac{10}{2^2} 2210 = 2 =2 =2 2 2 2^2 22 的贡献, 10 2 3 \dfrac{10}{2^3} 2310 = 1 =1 =1 2 3 2^3 23的贡献,因此很容易求出 1   n 1~n 1 n 2 2 2 的次数。而在这个过程是分母指数枚举到 n n n,所以求每一个次数的时间大概是 l o g n log_n logn
所以总的时间复杂度大概是 n l o g n ⋅ l o g n = O ( n ) \dfrac{n}{log_n}·log_n=O(n) lognnlogn=O(n)

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1000010;
int primes[N], cnt;
bool st[N];
void init(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[i * primes[j]] = true;
            if (i % primes[j] == 0) break;
        }
    }
}
int main()
{
    int n;
    cin >> n;
    init(n);
    for (int i = 0; i < cnt; i ++ )
    {
        int p = primes[i];
        int s = 0;
        for (int j = n; j; j /= p) s += j / p;
        printf("%d %d\n", p, s);
    }
    return 0;
}

AcWing 196. 质数距离(二次筛法)

给定两个整数 L L L U U U,你需要在闭区间 [ L , U ] [L,U] [L,U] 内找到距离最接近的两个相邻质数 C 1 C1 C1 C 2 C2 C2(即 C 2 − C 1 C2−C1 C2C1 是最小的),如果存在相同距离的其他相邻质数对,则输出第一对。

同时,你还需要找到距离最远的两个相邻质数 D 1 D1 D1 D 2 D2 D2(即 D 1 − D 2 D1−D2 D1D2 是最大的),如果存在相同距离的其他相邻质数对,则输出第一对。

输入格式
每行输入两个整数 L L L U U U,其中 L L L U U U 的差值不会超过 1 0 6 10^6 106

输出格式
对于每个 L 和 U,输出一个结果,结果占一行。

结果包括距离最近的相邻质数对和距离最远的相邻质数对。(具体格式参照样例)

如果 L 和 U 之间不存在质数对,则输出 There are no adjacent primes.。

数据范围
1 ≤ L < U ≤ 2 31 − 1 1≤L<U≤2^{31}−1 1L<U2311
输入样例:

2 17
14 17

输出样例:

2,3 are closest, 7,11 are most distant.
There are no adjacent primes.

思路:这道题最大的N的范围大概是 2 e 9 2e9 2e9,如果直接筛复杂度起码是线性的,而且 1 到 2 e 9 1到2e9 12e9之前的质数大概是 n l o g = 1 e 8 \dfrac{n}{log}=1e8 logn=1e8左右,数组也开不了那么大。所以我们干脆不筛 2 e 9 2e9 2e9范围内所有的质数,而转化为只筛 n \sqrt{n} n 范围内所有质数即可。
对 于 n 中 的 质 数 , 至 少 会 存 在 一 个 d 满 足 : d < = n d 对于n中的质数,至少会存在一个 d满足: d<=\dfrac{n}{d} ndd<=dn即最大的第一个质因数也不超过 n \sqrt{n} n 。所以我们可以只枚举前 n \sqrt{n} n 范围的的质数 ( n ≈ 50000 ) (\sqrt{n}≈50000) n 50000,对于这个范围内的每一个质数,再枚举它的倍数,然后删掉在 L 到 U L到U LU区间里的所有合数,这样对于L到U范围内剩下的数也是质数。(因为这个质数本身是合法的,所以筛其倍数的时候起码从两倍开始筛)
已知 L L L U U U 的差值不会超过 1 0 6 10^6 106,所以对于任意的一个 p p p,它最多的倍数个数是 1 0 6 p \dfrac{10^6}{p} p106,所以枚举每一个质数的倍数为:
1 0 6 2 + 1 0 6 3 + 1 0 6 5 + . . . . . . 1 0 6 p ( p 为 50000 内 的 质 数 ) \dfrac{10^6}{2}+\dfrac{10^6}{3}+\dfrac{10^6}{5}+......\dfrac{10^6}{p}(p为50000内的质数) 2106+3106+5106+......p106(p50000)

1 0 6 ( 1 2 + 1 3 + 1 5 + . . . . . . 1 p ) ( p 为 50000 内 的 质 数 ) 10^6(\dfrac{1}{2}+\dfrac{1}{3}+\dfrac{1}{5}+......\dfrac{1}{p}) (p为50000内的质数) 106(21+31+51+......p1)(p50000)

( 1 2 + 1 3 + 1 5 + . . . . . . 1 p ) 的 复 杂 度 大 概 是 l o g l o g n (\dfrac{1}{2}+\dfrac{1}{3}+\dfrac{1}{5}+......\dfrac{1}{p})的复杂度大概是log{log_n} (21+31+51+......p1)loglogn所以总复杂度为 n ∗ l o g l o g n ≈ O n n*log{log_n}≈O_n nloglognOn

做法:首先找到最小的大于L的p的倍数,然后筛掉区间内所有p的倍数即可。
这里用到一个公式 O 1 求 出 最 小 的 大 于 L 的 p 的 倍 数 O_1求出最小的大于L的p的倍数 O1Lp l + p − 1 p \dfrac{l + p - 1}{p} pl+p1——上取整公式。

  1. 如果 L L L% p = 0 p=0 p=0,那么L+p再-1下取整对答案无影响。
  2. 如果 L L L% p ! = 0 p !=0 p!=0,那么由 L L L% p > = 1 p>=1 p>=1可知 l + p − 1 l + p - 1 l+p1再除p相当于多了1。

所以枚举的起点为: l + p − 1 p ∗ p \dfrac{l + p - 1}{p}*p pl+p1p


#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

typedef long long LL;

const int N = 1000010;

int primes[N], cnt;
bool st[N];

void init(int n)
{
    memset(st, 0, sizeof st);
    cnt = 0;
    for (int i = 2; i <= n; i ++ )
    {
        if (!st[i]) primes[cnt ++ ] = i;
        for (int j = 0; primes[j] * i <= n; j ++ )
        {
            st[i * primes[j]] = true;
            if (i % primes[j] == 0) break;
        }
    }
}

int main()
{
    int l, r;
    while (cin >> l >> r)
    {
        init(50000);//筛根号n个数

        memset(st, 0, sizeof st);
        for (int i = 0; i < cnt; i ++ )
        {
            LL p = primes[i];
            for (LL j = max(p * 2, (l + p - 1) / p * p); j <= r; j += p)
                st[j - l] = true;//用偏移量来筛掉合数可以使得数组不会开太大
        }

        cnt = 0;
        for (int i = 0; i <= r - l; i ++ )
            if (!st[i] && i + l >= 2) //合数不能是1
                primes[cnt ++ ] = i + l;

        if (cnt < 2) puts("There are no adjacent primes.");//如果统计到的质数不足两个
        else
        {
            int minp = 0, maxp = 0;
            for (int i = 0; i + 1 < cnt; i ++ )
            {
                int d = primes[i + 1] - primes[i];
                if (d < primes[minp + 1] - primes[minp]) minp = i;
                if (d > primes[maxp + 1] - primes[maxp]) maxp = i;
            }

            printf("%d,%d are closest, %d,%d are most distant.\n",
                primes[minp], primes[minp + 1],
                primes[maxp], primes[maxp + 1]);
        }
    }

    return 0;
}

198. 反素数

在这里插入图片描述
思路:抓住几点性质之后暴搜。
在这里插入图片描述

#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

typedef long long LL;

int primes[9] = {2, 3, 5, 7, 11, 13, 17, 19, 23};
int maxd, number;
int n;

void dfs(int u, int last, int p, int s)//枚举到第几个质数,每个质数递减的指数,当前的这个数是谁,这个数的约数个数
{
    if (s > maxd || s == maxd && p < number)//找约数最多且最小的数
    {
        maxd = s;
        number = p;
    }

    if (u == 9) return;//枚举不超过9种约数

    for (int i = 1; i <= last; i ++ )//枚举当前这个约束的每一种指数
    {
        if ((LL)p * primes[u] > n) break; 
        p *= primes[u];
        dfs(u + 1, i, p, s * (i + 1));
    }
}

int main()
{
    cin >> n;

    dfs(0, 30, 1, 1);//枚举到第几个质数,每个质数递减的指数,当前的这个数是谁,这个数的约数个数

    cout << number << endl;

    return 0;
}

1273. 天才的记忆

题意:给定一个序列,查询某个区间的最大值。
思路
f [ i ] [ j ] f[i][j] f[i][j] 表示从 i i i 开始,长度是 2 j 2^j 2j的区间中的最值。
f [ i ] [ j ] = m a x ( f [ i ] [ j − 1 ] , f [ i + 2 j − 1 , j − 1 ] ) f[i][j]=max(f[i][j-1],f[i+2^{j-1},j-1]) f[i][j]=max(f[i][j1],f[i+2j1,j1])
在这里插入图片描述

在这里插入图片描述
找到一个 k k k 使得 2 k < = l e n 2^k<=len 2k<=len 的最大的 k k k ,那么 2 ⋅ 2 k > l e n 2·2^{k}>len 22k>len,所以枚举 ( l , k ) 和 ( r − 2 k − 1 , r ) (l,k)和(r-2^k-1,r) (l,k)(r2k1,r)就可以取得这个区间所有的最值情况。
那么如何快速找到使得 2 k < = l e n 2^k<=len 2k<=len 的最大的 k k k呢:
k = [ l o g 2 l e n ] 向 下 取 整 k=[log_2{len}]向下取整 k=[log2len]
k = [ l o g 10 l e n l o g 10 2 ] 向 下 取 整 k=[\frac{log_{10}len}{log_{10} 2}]向下取整 k=[log102log10len]
这一步可以使用log函数——返回以10为底的 l o g log log数。

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>

using namespace std;

const int N = 200010, M = 18;

int n, m;
int w[N];
int f[N][M];

void init()
{
    for (int j = 0; j < M; j ++ )
        for (int i = 1; i + (1 << j) - 1 <= n; i ++ )
            if (!j) f[i][j] = w[i];
            else f[i][j] = max(f[i][j - 1], f[i + (1 << j - 1)][j - 1]);
}

int query(int l, int r)
{
    int len = r - l + 1;
    int k = log(len) / log(2);

    return max(f[l][k], f[r - (1 << k) + 1][k]);
}

int main()
{
    scanf("%d", &n);
    for (int i = 1; i <= n; i ++ ) scanf("%d", &w[i]);

    init();

    scanf("%d", &m);
    while (m -- )
    {
        int l, r;
        scanf("%d%d", &l, &r);
        printf("%d\n", query(l, r));
    }

    return 0;
}

220. 最大公约数

在这里插入图片描述
思路: g c d ( x , y ) = p [ 质 数 ] , 那 么 显 然 g c d ( x p , y p ) = 1 gcd(x,y)=p[质数],那么显然gcd(\dfrac{x}{p},\dfrac{y}{p})=1 gcd(x,y)=p[]gcd(px,py)=1, 此时可以根据互质定义转化为求 1 1 1~ N p \dfrac{N}{p} pN范围内的欧拉函数前缀和。
从图上欧拉函数的分布来看,由 x , y x,y x,y 相互形成的欧拉函数的和可以表示为 S n = 2 ⋅ ( φ 2 + φ 3 + . . . . . . φ n ) + 1 S_n=2·(φ_2+φ_3+......φ_n)+1 Sn=2(φ2+φ3+......φn)+1(1是由对角线上(1,1)这对点形成的) ,之后我们可以求出 S n S_n Sn的前缀和, 使得 O 1 O_1 O1时间查找。
在这里插入图片描述

最后再枚举1到N之间所有的质数 p p p,求出所有的 S [ n / p ] S[n/p] S[n/p]的前缀和 。

#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

typedef long long LL;

const int N = 1e7 + 10;

int primes[N], cnt;
bool st[N];
int phi[N];
LL s[N];

void init(int n)
{
    for (int i = 2; i <= n; i ++ )
    {
        if (!st[i])
        {
            primes[cnt ++ ] = i;
            phi[i] = i - 1;
        }
        for (int j = 0; primes[j] * i <= n; j ++ )
        {
            st[primes[j] * i] = true;
            if (i % primes[j] == 0)
            {
                phi[i * primes[j]] = phi[i] * primes[j];
                break;
            }
            phi[i * primes[j]] = phi[i] * (primes[j] - 1);
        }
    }

    for (int i = 1; i <= n; i ++ ) s[i] = s[i - 1] + phi[i];//Sn前缀和
}

int main()
{
    int n;
    cin >> n;
    init(n);

    LL res = 0;
    for (int i = 0; i < cnt; i ++ )//把所有可能的Sn加起来
    {
        int p = primes[i];
        res += s[n / p] * 2 + 1;
    }

    cout << res << endl;

    return 0;
}

AcWing 222. 青蛙的约会

题意:两只青蛙同向围着地球跳,求和时相遇。
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
注意这里求得的 x x x应该是通解,应该把它取模到最小正整数里。
在这里插入图片描述

#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

typedef long long 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()
{
    LL a, b, m, n, L;
    cin >> a >> b >> m >> n >> L;

    LL x, y;
    LL d = exgcd(m - n, L, x, y);
    if ((b - a) % d) puts("Impossible");
    else
    {
        x *= (b - a) / d;
        LL t = abs(L / d);//把x取模到最小正整数里
        cout << (x % t + t) % t << endl;
    }

    return 0;
}


约数之和【求等比数列】

在这里插入图片描述
在这里插入图片描述
思路:显然对于一个 A A A可以用分解定理: A = p 1 a 1 ⋅ p 2 a 2 ⋅ . . . . . . ⋅ p k a k A=p_1^{a1}·p_2^{a2}·......·p_k^{ak} A=p1a1p2a2......pkak,那么对于 A B A^B AB,显然 A B = p 1 a 1 ‘ B ⋅ p 2 a 2 ⋅ B ⋅ . . . . . . ⋅ p k a k ⋅ B A^B=p_1^{a1`B}·p_2^{a2·B}·......·p_k^{ak·B} AB=p1a1Bp2a2B......pkakB。由约数之和的公式可知,约数之和等于一堆括号相乘,每一个括号内部是某个因子从0到因数最高次之和。因为对于一个数 m ( m < = 5 e 10 m<=5e10 m<=5e10) 最多只会有 l o g 2 m log_2m log2m个质因子,几十个,所以括号不多,但是括号内部因为B的范围很大,所以从 0 0 0 a i ⋅ B a_i·B aiB之间有很多项需要求和。
这里解决很多项求和有两个办法:再去听听快速幂求逆元
1.等比数列求和公式+快速幂求逆元

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long LL;
const int mod=9901;
LL qmi(LL a,LL k)
{
    LL res= 1;
    while(k)
    {
        if(k & 1) res=res*a %mod;
        a= a*a %mod;
        k >>= 1;
    }
    return res;
}
LL mul(LL p,LL k) //等比数列求和公式,分母转化为求逆元
{   //费马小定理条件
    if((p-1)%mod!=0) return (qmi(p,k + 1) - 1 + mod) % mod * (qmi(p - 1,mod - 2) )% mod;
    else return k+1%mod;//p-1%mod==0,那么p的每一次幂对应项都会%mod==1,共k+1项
}
int main()
{
    LL n,m;
    cin>>n>>m;
    LL res=1;
    for(int i=2;i<=n/i;i++)
    {
        LL s=0;
        if(n%i==0)
        {
            while(n%i==0)
            {
                n/=i;
                s++;
            }
            res=res*mul(i,s*m)%mod;
        }
    }
    if(n>1) res=res*mul(n,1*m)%mod;
    if(n==0) res=0;
    cout<<(res%mod+mod)%mod<<endl;
}

2.分治等比数列+递归
把一个等比数列从中间分开,通过讨论长度,提公因式实现递归。
参数要传数组长度,传长度-1的那个指数下标会出问题。

#include <cstdio>

const int mod = 9901;

int qmi(int a, int k)
{
    int res = 1;
    a %= mod;
    while (k)
    {
        if (k & 1) res = res * a % mod;
        a = a * a % mod;
        k >>= 1;
    }
    return res;
}

int sum(int p, int k)
{
    if (k == 1) return 1;
    if (k % 2 == 0) return (1 + qmi(p, k / 2)) * sum(p, k / 2) % mod;
    return (sum(p, k - 1) + qmi(p, k - 1)) % mod;
}

int main()
{
    int a, b;
    scanf("%d%d", &a, &b);

    int res = 1;
    // 对a分解质因数
    for (int i = 2; i * i <= a; i ++ )
        if (a % i == 0)
        {
            int s = 0;
            while (a % i == 0)
            {
                a /= i, s ++ ;
            }
            res = res * sum(i, b * s + 1) % mod;//第二个参数传长度
        }

    if (a > 1) res = res * sum(a, b + 1) % mod;
    if (a == 0) res = 0;

    printf("%d\n", res);

    return 0;
}


AcWing 1298. 曹冲养猪

中国剩余定理板子题。

#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

typedef long long LL;

const int N = 10;

int n;
int A[N], B[N];

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()
{
    scanf("%d", &n);

    LL M = 1;
    for (int i = 0; i < n; i ++ )
    {
        scanf("%d%d", &A[i], &B[i]);
        M *= A[i];
    }

    LL res = 0;
    for (int i = 0; i < n; i ++ )
    {
        LL Mi = M / A[i];
        LL ti, x;
        exgcd(Mi, A[i], ti, x);
        res += B[i] * Mi * ti;
    }

    cout << (res % M + M) % M << endl;

    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值