数论

数论 —— 解题能力特训营

式子真的好难写

费马小定理

\(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\times \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} \times 5 \equiv 1^{10000} \times 5(\mod 12)\)

拓展欧拉定理:

\[a^b \equiv \begin{cases} a^{b\mod \phi(p)},\quad \gcd(a, p) = 1\\ \\ a ^ b, \quad \gcd(a, p) \not= 1, b < \phi(p)\\ \\ a^{b\mod \phi(p) + \phi(p)},\quad \gcd(a, p) \not= 1, b >= \phi(p) \end{cases} \tag{mod p} \]

第三个式子似乎与第一个式子的区别,就是为了应对 $ \gcd(a, p) \not= 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\n", solve(mod));
	}
	return 0;
}

互质的数之和

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

\(n\le 10^{14}\)

题解:

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

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

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

[BZOJ2190] 仪仗队

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

\(1\le N\le 40000\)

题解:

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

/*
Date:
Source:luogu 2158
konwledge:求互质的数的对数 phi函数前缀和 
*/
#include <cstdio>
#include <iostream>
#define orz cout << "AK IOI" <<"\n"

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\n", ans.size());
    for(int i = 0; i < ans.size(); i++)
    printf("%d %d\n", 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\)​​​即可。​

\[\begin{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} \]

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

\[\begin{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)\)

\[\begin{cases} X = x + \frac{b}{\gcd(a, b)} \times k \\ \\ Y = y - \frac{a}{gcd(a, b)} \times 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(\lfloor\frac ni \rfloor)^5 \times i \]

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

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

代码实现简单优雅,比如上述题目 \(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^{n\mod p}_{m\mod p} \times C^{\lfloor \frac n p⌋}_{⌊\frac mp⌋}(\mod p)\)

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

posted @ 2021-08-21 21:50  _程门立雪  阅读(136)  评论(3编辑  收藏  举报