• 数论


    数论 —— 解题能力特训营

    式子真的好难写

    费马小定理

    (p) 是一个质数, 且 (a) 不是 (p) 的倍数。

    [a^{p - 1}equiv 1( mod p) ]

    这个定理一般不用,因为太简单

    欧拉定理

    (a,p)​​ 互质,

    [a^{phi(p)} equiv 1(mod p) ]

    欧拉函数:(phi(p))​ 表示的是小于等于 (p) 中和 (p) 互质的数的个数。

    单个欧拉函数的求解, 其中 (s_i)(p) 的质因数。

    [phi(p)=p imes prod frac{s_i-1}{s_i} ]

    如果数 (x,y) 互质, 则可以 (phi(xy)=phi(x)phi(y))​。

    常用于对指数取模

    举个例子:(5 ^ 4 equiv 1 (mod 12))

    (5 ^ {40001} = (5^4)^{10000} imes 5 equiv 1^{10000} imes 5(mod 12))

    拓展欧拉定理:

    [a^b equiv egin{cases} a^{bmod phi(p)},quad gcd(a, p) = 1\ \ a ^ b, quad gcd(a, p) ot= 1, b < phi(p)\ \ a^{bmod phi(p) + phi(p)},quad gcd(a, p) ot= 1, b >= phi(p) end{cases} ag{mod p} ]

    第三个式子似乎与第一个式子的区别,就是为了应对 $ gcd(a, p) ot= 1, b >= phi(p)$​的情况。

    (手玩一下:将(6^6 mod 6)​ 分别代入式子1和式子3)

    欧拉线性筛:

    const int maxn = 1e7+5;
    int prime[maxn]; //素数表
    int phi[maxn]; // phi[]数组就是1~maxn的欧拉函数值 
    int visit[maxn];
    void ol()
    {
        int cnt = 0;
    	for(int i=2;i<=maxn;i++)
        {
    	    if(!visit[i])
            {
    	        prime[++cnt]=i;
    	        phi[i]=i-1;//质数直接能求出
    	    }
    	    for(int j=1;j<=cnt&&prime[j]*i<=maxn;j++)
            {
    	        visit[i*prime[j]] = 1;
    	        if(i%prime[j]==0)//px不互质
                {
    	            phi[i*prime[j]]=phi[i]*prime[j];
    	            break;
    	        }
    	        else phi[i*prime[j]]=phi[i]*(prime[j]-1);//px互质
    	    }
    	}
    } 
    

    [BZOJ 3884] 上帝与集合的正确用法

    递归调用, 用扩展欧拉定理。

    [2^{2^{2^{2^{…}}}}mod p ]

    对指数取模, 利用扩展欧拉定理。

    每次都将 2 的指数看做一个整体,会发现这是一个递归结构,

    我们递归调用,当 $phi(p) $ 为 (1) 时,直接返回答案,因为任何数(mod 1) 都是原数。

    每进行两次递归,(p)​ 至少会减半,所以只会递归 (log(p))

    /*
    Date:2021.8.21
    Source:luogu 4139 
    konwledge:扩展欧拉定理 
    */
    #include <iostream>
    #include <cstdio>
    #define orz cout << "AK IOI"
    
    using namespace std;
    const int maxn = 1e7 + 10;
    
    inline int read()
    {
    	int f = 0, x = 0; char ch = getchar();
    	while(!isdigit(ch)) f |= (ch == '-'), ch = getchar();
    	while(isdigit(ch)) x = x * 10 + (ch ^ 48), ch = getchar();
    	return f ? -x : x;
    }
    inline void print(int X)
    {
    	if(X < 0) {X = ~(X - 1); putchar('-');}
    	if(X > 9) print(X / 10);
    	putchar(X % 10 + '0');
    }
    int T, mod; 
    int cnt, vis[maxn], phi[maxn], prime[maxn];
    void ol()
    {
    	prime[1] = 1, phi[1] = 1;
    	for(int i = 2; i <= maxn; i++)
    	{
    		if(!vis[i]) {prime[++cnt] = i; phi[i] = i - 1;}
    		for(int j = 1; i * prime[j] <= maxn && j <= cnt; j++)
    		{
    			vis[i * prime[j]] = 1;
    			if(i % prime[j] == 0)
    			{
    				phi[i * prime[j]] = phi[i] * prime[j];
    				break;
    			}
    			else phi[i * prime[j]] = phi[i] * (prime[j] - 1);
    		}
    	}
    }
    int pow(long long a, int n, int mod)
    {
    	long long ans = 1;
    	while(n)
    	{
    		if(n & 1) ans = ans * a % mod;
    		a = a * a % mod;
    		n >>= 1;
    	}
    	return ans;
    }
    int solve(int mod)
    {
    	if(mod == 1) return 0;
    	return pow(2, solve(phi[mod]) + phi[mod], mod);
    }
    int main()
    {
    	//freopen(".in","r",stdin);
        //freopen(".out","w",stdout);
        T = read();
        ol();
        while(T--)
        {
        	mod = read();
        	printf("%d
    ", solve(mod));
    	}
    	return 0;
    }
    
    

    互质的数之和

    给你 (n)​,需要求出 ([1,n])​ 中与 (n)​​ 互质的数的和。

    (nle 10^{14})

    题解:

    由于 (gcd(i,n)=gcd(n-i,n))

    把与 (n)​ 互质的数放在一起,发现我们总能找到两个和 (n)​ 互质的数相加等于 (n)​​。

    发现答案为:(n imes frac{phi(n)}{2})​。

    [BZOJ2190] 仪仗队

    (sum_{1le i,jle N} [gcd(i,j)=1])​​

    (1le Nle 40000)

    题解:

    $phi $​​​函数的前缀和, 乘以2。

    /*
    Date:
    Source:luogu 2158
    konwledge:求互质的数的对数 phi函数前缀和 
    */
    #include <cstdio>
    #include <iostream>
    #define orz cout << "AK IOI" <<"
    "
    
    using namespace std;
    const int maxn = 40010;
    
    inline int read()
    {
    	int x = 0, f = 1;
    	char ch = getchar();
    	while (ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
    	while (ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + (ch ^ 48);ch = getchar();}
    	return x * f;
    }
    int n, cnt, vis[maxn], phi[maxn], prime[maxn];
    long long ans;
    void ol()
    {
    	phi[1] = 1, prime[1] = 1;
    	for(int i = 2; i <= maxn; i++)
    	{
    		if(!vis[i]){prime[++cnt] = i; phi[i] = i - 1;}
    		for(int j = 1; i * prime[j] <= maxn && j <= cnt; j++)
    		{
    			vis[i * prime[j]] = 1;
    			if(i % prime[j] == 0)//只被最小的筛 
    			{
    				phi[i * prime[j]] = phi[i] * prime[j];
    				break;
    			}
    			else phi[i * prime[j]] = phi[i] * (prime[j] - 1);
    		}
    	}
    } 
    int main()
    {
        //freopen(".in","r",stdin);
        //freopen(".out","w",stdout); 
        n = read();
        if(n == 1) {puts("0"); return 0;}
    	ol();
        for(int i = 1; i <= n - 1; i++) ans += 1ll * phi[i];
    	printf("%lld", ans * 2 + 1);
    	return 0;
    }
    
    

    Prime Swaps

    给出长度为 (N) 的排列,每次你可以选择两个位置 (i,j) 并交换上边的数,前提是 (j - i + 1) 是质数。

    要求在 (5N) 次操作内,把这个序列排号,输出具体排列的操作。

    题解:

    哥德巴赫猜想: 任意大于二的偶数,都可表示成两个素数之和。

    那么我们每次放心的移动就好了!

    那我们贪心的按照 (1, 2, 3, 4) …… 的顺序来排,(就是让 (1) 到第一位置, (2) 到第二位置),哥德巴赫猜想保证了我们每次都可以移得动。

    每次我们把一个数往前移动 (x)​ 距离时,每步先移动一个尽量大的质数距离即可。

    然后由于质数密度是 (ln(n))​,所以可以保证在 (5N)​​ 次一定可以完成。

    代码中预处理哪些数是质数,以及每个数比它小的最大质数是多少。

    /*
    Date:2021.8.21
    Source:CF432C
    konwledge:哥德巴赫猜想:任意大于二的偶数,都可表示成两个素数之和。
    */
    #include <iostream>
    #include <cstdio>
    #include <vector>
    #include <map>
    #define orz cout << "AK IOI"
    
    using namespace std;
    const int maxn = 1e5 + 10;
    
    inline int read()
    {
    	int f = 0, x = 0; char ch = getchar();
    	while(!isdigit(ch)) f |= (ch == '-'), ch = getchar();
    	while(isdigit(ch)) x = x * 10 + (ch ^ 48), ch = getchar();
    	return f ? -x : x;
    }
    inline void print(int X)
    {
    	if(X < 0) {X = ~(X - 1); putchar('-');}
    	if(X > 9) print(X / 10);
    	putchar(X % 10 + '0');
    }
    int n, a[maxn];
    map<int, int> m;
    int cnt, flag[maxn];
    bool vis[maxn], prime[maxn];
    vector<pair<int, int> > ans;
    void init()
    {
    	prime[1] = 1;
    	for(int i = 2; i <= maxn; i++) 
    	{
    		if(!vis[i]) 
    		{
    			prime[i] = 1;
    			for(int j = i; j <= maxn; j += i) vis[j] = 1;
    		}
    	}
    	for(int i = 2; i <= maxn; i++)
    		if(prime[i]) flag[i] = i;
    		else flag[i] = flag[i - 1];
    }
    int main()
    {
    	//freopen(".in","r",stdin);
        //freopen(".out","w",stdout);
        n = read();
        init();
        for(int i = 1; i <= n; i++) 
        	a[i] = read(), m[a[i]] = i;
    	for(int i = 1; i <= n; i++)
    	{
    		while(m[i] != i)
    		{
    			int t = m[i] - i + 1;                  //需要移动的距离 
    			int step = m[i] - flag[t] + 1;         //flag[t] 小于需要移动的距离的最大的质数 
    			ans.push_back(make_pair(step, m[i]));
    			m[a[step]] = m[i];                     //交换 
    			swap(a[m[i]], a[step]);
    			m[i] = step;
    		}
    	} 
        printf("%d
    ", ans.size());
        for(int i = 0; i < ans.size(); i++)
        printf("%d %d
    ", ans[i].first, ans[i].second);
    	return 0;
    }
    
    

    GCD

    (sum _{1<= i, j <= n}[gcd(i,j) is prime])​。

    题解:

    对于每一个质数,对答案的贡献是 (sum _{1 <= i, j, <= frac n p}[gcd(i, j) = 1])

    /*
    Date:2021.8.21
    Source:luogu 2568
    konwledge:同仪仗队 ,不过要枚举质数。  
    */
    #include <iostream>
    #include <cstdio>
    #define orz cout << "AK IOI"
    #define int long long 
    
    using namespace std;
    const int maxn = 1e7 + 10;
    
    inline int read()
    {
    	int f = 0, x = 0; char ch = getchar();
    	while(!isdigit(ch)) f |= (ch == '-'), ch = getchar();
    	while(isdigit(ch)) x = x * 10 + (ch ^ 48), ch = getchar();
    	return f ? -x : x;
    }
    inline void print(int X)
    {
    	if(X < 0) {X = ~(X - 1); putchar('-');}
    	if(X > 9) print(X / 10);
    	putchar(X % 10 + '0');
    }
    int n, ans, sum[maxn]; 
    int cnt, vis[maxn], phi[maxn], prime[maxn];
    void ol()
    {
    	prime[1] = 1, phi[1] = 1;
    	for(int i = 2; i <= maxn; i++)
    	{
    		if(!vis[i]) {prime[++cnt] = i; phi[i] = i - 1;}
    		for(int j = 1; i * prime[j] <= maxn && j <= cnt; j++)
    		{
    			vis[i * prime[j]] = 1;
    			if(i % prime[j] == 0)
    			{
    				phi[i * prime[j]] = phi[i] * prime[j];
    				break;
    			}
    			else phi[i * prime[j]] = phi[i] * (prime[j] - 1);
    		}
    	}
    }
    
    signed main()
    {
    	//freopen(".in","r",stdin);
        //freopen(".out","w",stdout);
        n = read();
        ol();
        for(int i = 1; i <= n; i++) sum[i] = sum[i - 1] + phi[i];
        for(int i = 1; i <= cnt && prime[i] <= n; i++)
        ans += ((sum[n / prime[i]]) * 2) - 1; 
        printf("%lld", ans);
    	return 0;
    }
    
    

    longge的问题

    (sum _{1 <= i <= n} gcd(i, N))​​。

    题解:

    枚举所有的 d,然后用质因数分解求 (phi)​​​即可。​

    [egin{align*} &sum_{1 <= i <= n} gcd(i, n) \ &=sum _{d|n}d sum_{1 <=i <= frac nd}[gcd(i, frac nd) = 1] \ &=sum_{d|n}d phi(frac nd) end{align*} ]

    奇数国

    题解:

    建一棵线段树。

    考虑(phi)​​函数的定义,维护区间对应的乘积和乘积有哪些质因数。

    用 long long 进行压位。

    最大公约数与欧几里得算法

    最小公倍数

    例题:

    操场是 (n) 个格子围城的圆形,

    (m) 个同学,步长 (a_i),求不会经过的格子数。

    题解:

    明显答案为

    [n - frac{lcm(n, a_i)}{a_i} ]

    最大公约数,最小公倍数性质

    [egin{align*} &lcm(s) = prod _{T in S} gcd(T)^{(-1)^{T - 1}} \ \ &gcd(Fib(a), Fib(b)) = Fib(gcd(a, b)) \ \ &gcd(x^a - 1, x^b - 1) = x^{gcd(a, b)} - 1 end{align*} ]

    扩展欧几里得算法

    void gcd(int a, int b, int &d, int &x, int &y)//扩展欧几里得 
    {
    	if(b == 0){d = a; x = 1; y = 0;}
    	else {gcd(b, a % b, d, y, x); y -= (a / b) * x;}
    }
    

    假如知道了一组解 ((x, y)), 那么通解 ((X,Y))

    [egin{cases} X = x + frac{b}{gcd(a, b)} imes k \ \ Y = y - frac{a}{gcd(a, b)} imes k end{cases} ]

    中国剩余定理

    int n;
    int m[maxn], a[maxn];
    void gcd(int a, int b, int &d, int &x, int &y)//扩展欧几里得 
    {
    	if(b == 0){d = a; x = 1; y = 0;}
    	else {gcd(b, a % b, d, y, x); y -= (a / b) * x;}
    }
    int china(int n, int *m, int *a)
    {
    	int M = 1, d, y, x = 0;
    	for(int i = 1; i <= n; i++) M *= m[i];
    	for(int i = 1; i <= n; i++)
    	{
    		int w = M / m[i];
    		gcd(m[i], w, d, d, y);
    		x = (x + y * w * a[i]) % M;
    	}
    	return (x + M) % M;
    }
    signed main()
    {
        //freopen(".in","r",stdin); 
        //freopen(".out","w",stdout);
        n = read();
        for(int i = 1; i <= n; i++) 
    	m[i] = read(), a[i] = read();
        printf("%lld", china(n, m, a));
    	return 0;
    }
    
    

    整数分块

    数论中用的很多的小技巧。

    [sum_{i = 1}^n(lfloorfrac ni floor)^5 imes i ]

    因为 (lfloor frac ni floor) 只有 (O(√n)) 种不同的取值,遍历每一个(lfloor frac ni floor)​ 相同的区间即可。

    这个性质经常被用来优化复杂度,特别是在一些积性函数的题目中。

    代码实现简单优雅,比如上述题目 (f(a) = a^5, sum(i) = frac {(1+i)i}2)​​ 。

    for(int i = 1, last; i <= n; i = last + 1)
    {
    	int a = n / i;
    	last = n / a;
    	ret += f(a) * (sum(last) - sum(i - 1));
    }
    

    卢卡斯定理

    如果 (p) 是一个质数,有公式:

    (C^n_m ≡ C^{nmod p}_{mmod p} imes C^{lfloor frac n p⌋}_{⌊frac mp⌋}(mod p))

    当组合数太大,需要取模的时候用到!

  • 相关阅读:
    Ubuntu18.04安装RTX2080Ti+NVIDIA驱动+CUDA
    G++ 编译多个源文件
    线段树【递归版本】
    Linux 安装 python 指定版本--编译源码方式
    正则表达式高级替换
    【转载】Git忽略规则和.gitignore规则不生效的解决办法
    一次“惊险”的系统修复过程
    YOLO模型对图片中车辆的识别比对
    YOLOv3模型识别车位图片的测试报告(节选)
    在windows下用python调用darknet的yolo接口
  • 原文地址:https://www.cnblogs.com/yangchengcheng/p/15170673.html
Copyright © 2020-2023  润新知