解题营_数论
数论基础
芝士
费马小定理
若 \(p\) 为质数 则
欧拉定理
若 \(a \bot p\) 则
其中 \(\varphi(p)\) 表示小于等于 \(p\) 中和 \(p\) 互质的数的个数
有
其中 \(s_i\) 为 \(p\) 的质因数
如果 \(x \bot y\) 则
扩展欧拉定理
第三个式子相当于保留最后一个循环节 第一个式子中如果 \(a, p\) 不互质 直接用第一个式子可能会把质数模成 \(0\) 等稀奇古怪的情况
线性筛求欧拉函数
void pre() {
phi[1] = 1;
for(int i = 2; i ^ N + 1; i++) {
if(!vis[i]) prime[++cnt] = i, phi[i] = i - 1;
for(int j = 1; j ^ cnt + 1 && i * prime[j] <= N; j++)
{
vis[i * prime[j]] = 1;
if(i % prime[j]) phi[i * prime[j]] = phi[i] * phi[prime[j]];
else {phi[i * prime[j]] = phi[i] * prime[j]; break;}
}
}
}
最大公约数与欧几里得算法
对于两个正整数 \(a, b\) 其最大公约数定义为最大的 \(c\) 使 \(c \mid a\) 且 \(c \mid b\) 记 \(\gcd(a, b) = c\)
有
最小公倍数
对于两个正整数 \(a, b\) 其最小公倍数定义为最小的 \(c\) 使 \(a \mid c\) 且 \(b \mid c\) 记 \(lcm(a, b) = c\)
有
对于 \(lcm(a, b, c)\) 或 \(lcm(a, b, c, d)\) ...
有
最大公约数与最小公倍数的性质
证明思路: \(\min\max\) 反演
证明思路: \(f_{n + m} = f_mf_{n + 1} + f_{m - 1}f_n\)
证明思路: \(x^a - x^b = x^b(x^{a - b} - 1)\)
扩展欧几里得算法
略
中国剩余定理
求一元模线性方程组 \(x \equiv a_i \pmod p_i\) 的一个通解 \(p_i\) 两两互质
令 \(P_i = \frac {\prod_{j = 1}^np_j}{p_i}, t \equiv P_{i}^{-1} \pmod {p_i}\)
构造一个解 \(x \equiv \sum a_it_iP_i \pmod P\)
正确性: \(P_i \mod p_j = 0, t_iP_i \mod p_i = 1\)
整除分块
放到下面题目里面了
卢卡斯定理
如果 \(p\) 为质数 有
线性基
异或
\(01\) 串
高斯消元
题目
上帝与集合的正确用法
求
\[2^{2^{2^{2^{2^{\dots}}}}} \mod p \]\(1 \leq T \leq 10^3, 1 \leq p \leq 10^7\)
先特判 \(1\) 和 \(2\)
扩展欧拉定理
把右上角那一坨东西设为 \(a\) 则
问题由一坨东西模 \(p\) 变为了一坨东西模 \(\varphi(p)\)
对于 \(\varphi\) 函数 每两次至少跌落一半 当 \(\varphi\) 里面变成 \(1\) 之后就不需要的 也就是递归出口 所以复杂度为 \(\log\) 级别
代码
/*
Source:
*/
#include<cstdio>
#define int long long
#define pn putchar('\n')
/*----------------------------------------------------------*/
const int D = 1e7 + 7;
/*----------------------------------------------------------*/
int T, phi[D], prime[D], cnt;
bool vis[D];
/*----------------------------------------------------------*/
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;
}
void Print(int x) {if(x < 0) putchar('-'), x = -x; if(x > 9) Print(x / 10); putchar(x % 10 ^ 48);}
/*----------------------------------------------------------*/
void pre() {
phi[1] = 1;
for(int i = 2; i ^ D; i++)
{
if(!vis[i]) prime[++cnt] = i, phi[i] = i - 1;
for(int j = 1; j ^ cnt + 1 && i * prime[j] < D; j++)
{
vis[i * prime[j]] = 1;
if(i % prime[j]) phi[i * prime[j]] = phi[i] * phi[prime[j]];
else {phi[i * prime[j]] = phi[i] * prime[j]; break;}
}
}
}
int power(int x, int y, int mod) {int res = 1; for(; y; x = x * x % mod, y >>= 1) if(y & 1) res = res * x % mod; return res;}
int solve(int p) {
if(p == 1) return 0;
return power(2, solve(phi[p]) + phi[p], p);
}
/*----------------------------------------------------------*/
signed main() {
pre(); T = read(); while(T--) Print(solve(read())), pn;
return 0;
}
互质的数的和
给定 \(n\) 求 \(1\) 至 \(n\) 中与 \(n\) 互质的数的和
\(n \leq 10^{14}\)
由于 \(\gcd(a, n) = \gcd(n - a, n)\)
所以将与 \(n\) 互质的数放在一起 收尾配对 一对数的和为 \(n\)
因此答案为 \(n \times \frac {\varphi(n)}2\)
仪仗队
求
\[\sum_{i = 1}^n\sum_{j = 1}^n[\gcd(i, j) = 1] \]\(1 \leq n \leq 4 \times 10^ 4\)
求互质的数的对数
\(\varphi\) 的前缀和
代码
/*
Source: P2158 [SDOI2008] 仪仗队
*/
#include<cstdio>
/*----------------------------------------------------------*/
const int A = 4e4 + 7;
/*----------------------------------------------------------*/
int n, phi[A], prime[A], cnt, ans;
bool vis[A];
/*----------------------------------------------------------*/
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;
}
void Print(int x) {if(x < 0) putchar('-'), x = -x; if(x > 9) Print(x / 10); putchar(x % 10 ^ 48);}
/*----------------------------------------------------------*/
/*----------------------------------------------------------*/
int main() {
n = read(); if(n == 1) {Print(0); return 0;} phi[1] = 1;
for(int i = 2; i ^ n + 1; i++)
{
if(!vis[i]) prime[++cnt] = i, phi[i] = i - 1;
for(int j = 1; j ^ cnt + 1 && i * prime[j] <= n; j++)
{
vis[i * prime[j]] = 1;
if(i % prime[j]) phi[i * prime[j]] = phi[i] * phi[prime[j]];
else {phi[i * prime[j]] = phi[i] * prime[j]; break;}
}
}
for(int i = 1; i ^ n; i++) ans += phi[i];
Print(ans << 1 | 1);
return 0;
}
Prime Swaps
给出一个长为 \(n\) 的排列 每次选择两个位置 \(i, j\) 并交换上面两个数 前提是 \(j - i +1\) 为质数
要求在 \(5n\) 次操作内 将这个序列拍好 输出具体排序的操作
\(n \leq 10^5\)
哥德巴赫猜想 任一大于 \(2\) 的偶数 都可以表示为两个素数的和
那么每次将一个数向前移动 \(x\) 距离时 先移动一个尽量打的质数的距离即可
贪心从 \(1, 2, 3, 4, \dots\) 的顺序排即可
然后由于质数的密度为 \(\ln(n)\) 所以可以保证在 \(5n\) 次一定可以完成
GCD
求
\[\sum_{i = 1}^n\sum_{j = 1}^n[\gcd(i, j)\ is\ prime] \]\(1 \leq n \leq 10^7\)
对于每一个质数 对答案的贡献就是
预处理 \(\varphi\) 函数 求前缀和 枚举所有质数 对每个质数 \(O(1)\) 统计贡献
代码
/*
Source: P2568 GCD
*/
#include<cstdio>
#define int long long
/*----------------------------------------------------------*/
const int D = 1e7 + 7;
/*----------------------------------------------------------*/
int n, phi[D], prime[D], cnt, sum[D], ans;
bool vis[D];
/*----------------------------------------------------------*/
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;
}
void Print(int x) {if(x < 0) putchar('-'), x = -x; if(x > 9) Print(x / 10); putchar(x % 10 ^ 48);}
/*----------------------------------------------------------*/
void pre() {
phi[1] = 1;
for(int i = 2; i ^ n + 1; i++)
{
if(!vis[i]) prime[++cnt] = i, phi[i] = i - 1;
for(int j = 1; j ^ cnt + 1 && i * prime[j] <= n; j++)
{
vis[i * prime[j]] = 1;
if(i % prime[j]) phi[i * prime[j]] = phi[i] * phi[prime[j]];
else {phi[i * prime[j]] = phi[i] * prime[j]; break;}
}
}
for(int i = 1; i ^ n + 1; i++) sum[i] = sum[i - 1] + phi[i];
}
/*----------------------------------------------------------*/
signed main() {
n = read(); pre();
for(int i = 1; i ^ cnt + 1 && prime[i] <= n; i++) ans += (sum[n / prime[i]] << 1) - 1;
Print(ans);
return 0;
}
互质对数
给定正整数 \(n\) 求多少个二元组 \((i, j)\) 满足
- \(1 \leq i < \leq n\)
- \(j + i\) 与 \(j - i\) 互质
\(n \leq 10^7, T \leq 10^5\)
求
即
如果令 \(t = j - i\) 再枚举一个 \(j\) 则问题转化为有多少对 \((i, t)\) 满足 \(i + t = j\) 且 \(\gcd(i, t) = 1\) 同时 \(t\) 为奇数
分 \(j\) 的奇偶讨论
- \(j\) 为偶数 此时 \((i, t)\) 的对数为 \(\varphi(j)\)
- \(j\) 为奇数 此时 \((i, t)\) 的对数为 \(\frac {\varphi(j)}2\)
上面那个是因为 \(i, t\) 都是奇数 可以互换
下面那个是因为 一奇一偶 一个分给 \(i\) 一个分给 \(t\)
预处理上面的前缀和 每次 \(O(1)\) 回答
Longge的问题
求
\[\sum_{i = 1}^n\gcd(i, n) \]\(1 \leq n \leq 2^{32}\)
化
枚举所有的 \(d\) 质因数分解硬求 \(\varphi\)
代码
/*
Source: P2303 [SDOI2012] Longge 的问题
*/
#include<cstdio>
#define int long long
/*----------------------------------------------------------*/
int n, ans;
/*----------------------------------------------------------*/
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;
}
void Print(int x) {if(x < 0) putchar('-'), x = -x; if(x > 9) Print(x / 10); putchar(x % 10 ^ 48);}
/*----------------------------------------------------------*/
int phi(int x) {
int res = x;
for(int i = 2; i * i <= x; i++)
{
if(!(x % i)) res = res / i * (i - 1);
while(!(x % i)) x /= i;
}
if(x > 1) res = res / x * (x - 1);
return res;
}
/*----------------------------------------------------------*/
signed main() {
n = read();
for(int i = 1; i * i <= n; i++) if(!(n % i))
ans += i * phi(n / i) + (n / i != i) * (n / i) * phi(i);
Print(ans);
return 0;
}
奇数国
一个长度为 \(n\) 的序列 每个数都是前 \(60\) 小的质数之一 两种操作一共 \(M\) 个
- 修改一个位置的数为某前 \(60\) 小的质数之一
- 查询一个区间乘积的 \(\varphi\) 值
对 \(10^9 + 7\) 取模
\(1 \leq n, m \leq 10^5\)
线段树 考虑维护什么
单点修改可以直接改
需要维护区间乘积的 \(\varphi\) 值
维护一个区间乘积 维护有哪些质因数 质因数只有 \(60\) 个 压一下
可以直接合并 乘积直接相乘 质因数或一下 根据定义维护 \(\varphi\) 值 直接暴力维护
代码
/*
Source: P4140 奇数国
*/
#include<cstdio>
#define int long long
#define pn putchar('\n')
/*----------------------------------------------------------*/
const int B = 1e5 + 7;
const int mod = 19961993;
/*----------------------------------------------------------*/
int n, prime[70], cnt, inv[70];
bool vis[300];
/*----------------------------------------------------------*/
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;
}
void Print(int x) {if(x < 0) putchar('-'), x = -x; if(x > 9) Print(x / 10); putchar(x % 10 ^ 48);}
/*----------------------------------------------------------*/
void pre() {
for(int i = 2; i ^ 282; i++)
{
if(!vis[i]) prime[++cnt] = i;
for(int j = 1; j ^ cnt + 1 && i * prime[j] <= 281; j++)
{
vis[i * prime[j]] = 1;
if(!(i % prime[j])) break;
}
}
}
int power(int x, int y) {int res = 1; for(; y; x = x * x % mod, y >>= 1) if(y & 1) res = res * x % mod; return res;}
namespace Seg {
#define ls(x) x << 1
#define rs(x) x << 1 | 1
#define mid (t[p].l + t[p].r >> 1)
struct node {int l, r, prod, S;} t[B << 2];
void f(int p, int k) {
t[p].prod = k; int s = 0;
for(int i = 1; i ^ cnt + 1; i++)
if(!(k % prime[i])) s |= 1ll << i - 1;
t[p].S = s;
}
node operator + (node x, node y) {
node z; z.l = x.l; z.r = y.r;
z.prod = x.prod * y.prod % mod;
z.S = x.S | y.S; return z;
}
void build(int p, int l, int r) {
t[p].l = l; t[p].r = r; if(l == r) {f(p, 3); return ;}
build(ls(p), l, mid); build(rs(p), mid + 1, r);
t[p] = t[ls(p)] + t[rs(p)];
}
void up_date(int p, int pos, int k) {
if(t[p].l == pos && t[p].r == pos) {f(p, k); return ;}
if(pos <= mid) up_date(ls(p), pos, k); else up_date(rs(p), pos, k);
t[p] = t[ls(p)] + t[rs(p)];
}
node query(int p, int l, int r) {
if(l <= t[p].l && t[p].r <= r) return t[p];
if(l <= mid) if(r > mid) return query(ls(p), l, r) + query(rs(p), l, r);
else return query(ls(p), l, r); else return query(rs(p), l, r);
}
}
void Main() {
pre(); n = read(); Seg::build(1, 1, 1e5);
for(int i = 1; i ^ cnt + 1; i++) inv[i] = power(prime[i], mod - 2) % mod;
for(int i = 1; i ^ n + 1; i++)
if(read())
{
int x = read(), y = read();
Seg::up_date(1, x, y);
}
else
{
int x = read(), y = read(); Seg::node tmp = Seg::query(1, x, y);
int sum = tmp.prod, s = tmp.S;
for(int i = 1; i ^ cnt + 1; i++) if(1ll << i - 1 & s)
sum = sum * inv[i] % mod * (prime[i] - 1) % mod;
Print(sum); pn;
}
}
/*----------------------------------------------------------*/
signed main() {Main(); return 0;}
pre
给一个序列 \(s\) (可能有重复的元素 而相同元素交换位置依然是同一个排列) 求 \(s\) 的排名 对 \(m\) 取模
\(n \leq 3 \times 10^5\)
\(m\) 不一定为质数
跑路了...
对模数不是质数 将模数进行分解 转化为模 \(p^k\) 其中 \(p\) 为 \(m\) 的质因数
在对 \(p\) 取模的运算中 需要记录 \(k = xp^y\) 中的 \(x\) 与 \(y\)
其中 \(x\) 对 \(p\) 取模 \(y\) 对 \(\varphi(p)\) 取模 最后 CRT 起来
整数分块
求
\[\sum_{i = 1}^n\left(\lfloor\frac ni \rfloor\right)^5 \times i \]\(n \leq 10^9\) 答案对 \(10^9 + 7\) 取模
\(\lfloor\frac ni \rfloor\) 只有 \(O(\sqrt n)\) 种取值 遍历每个取值相同的区间即可
常用来优化复杂度 积性函数的题目
比如上面这个题目中 设 \(f_a = a^5, sum_i = \frac {(i + 1)i}2\)
把上面那个东西写成整除分块的形式 大概就是这样的一坨东西:
示例代码
for(int i = 1, last; i <= n; i = last + 1)
{
int a = n / i; last = n / a;//区间终点
ans += f(a) * (sum(last) - sum(i - 1));
}
例题
求
\[\sum_{a = 1}^n\sum_{b = 1}^n\gcd(x^a - 1, x^b - 1) \]\(x, n \leq 10^6, T \leq 10^3\) 答案对 \(10^9 + 7\) 取模
化:
对后面那一坨东西预处理前缀和 可以枚举 \(k\) 对每一次询问 \(O(n)\) 的询问
有一千组询问 显然可以超时
后面那一坨东西只与 \(k\) 有关 且在一段里面是相同的
整除分块(等比数列化简) :
对 \(\varphi\) 处理前缀和之后 每次询问 \(O(\sqrt n)\)
古代猪文
给定 \(N, k, G\) 求
\[G^{\sum_{k \mid N}{N \choose k}} \pmod {999911658} \]\(n \leq 10^9\)
强行卢卡斯 + 强行 CRT
给的 \(n\) 范围奇大 强行卢卡斯
模数不给质数 强行 CRT
出题人不是东西 石锤了
枚举 \(k\) 卢卡斯求组合数 最后 CRT 起来
/*
Source: P2480 [SDOI2010]古代猪文
*/
#include<cstdio>
#include<cstring>
#define int long long
#define pt putchar(' ')
#define pn putchar('\n')
#define Abs(x) ((x) < 0 ? -(x) : (x))
#define Max(x, y) ((x) > (y) ? (x) : (y))
#define Min(x, y) ((x) < (y) ? (x) : (y))
#define Swap(x, y) ((x) ^= (y) ^= (x) ^= (y))
/*----------------------------------------------------------*/
const int A = 5e4 + 7;
const int B = 1e5 + 7;
const int D = 1e7 + 7;
const int mod = 999911658;
const int INF = 0x3f3f3f3f;
/*----------------------------------------------------------*/
inline void File() {
freopen(".in", "r", stdin);
freopen(".out", "w", stdout);
}
/*----------------------------------------------------------*/
int n, G, fac[A], a[5], b[5] = {0, 2, 3, 4679, 35617}, ans;
/*----------------------------------------------------------*/
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;
}
void Print(int x) {if(x < 0) putchar('-'), x = -x; if(x > 9) Print(x / 10); putchar(x % 10 ^ 48);}
/*----------------------------------------------------------*/
int power(int x, int y, int p) {int res = 1; for(; y; x = x * x % p, y >>= 1) if(y & 1) res = res * x % p; return res;}
void pre(int p) {fac[0] = 1; for(int i = 1; i ^ p + 1; i++) fac[i] = fac[i - 1] * i % p;}
int C(int n, int m, int p) {if(n < m) return 0; return fac[n] * power(fac[m], p - 2, p) % p * power(fac[n - m], p - 2, p) % p;}
int lucas(int n, int m, int p) {if(n < m) return 0; if(!n) return 1; return lucas(n / p, m / p, p) * C(n % p, m % p, p) % p;}
void crt() {for(int i = 1; i ^ 5; i++) ans = (ans + a[i] * (mod / b[i]) % mod * power(mod / b[i], b[i] - 2, b[i]) % mod);}
void Main() {
n = read(); G = read(); if(!(G % (mod + 1))) {Print(0); return ;}
for(int k = 1; k ^ 5; k++)
{
pre(b[k]);
for(int i = 1; i * i <= n; i++) if(!(n % i))
{
a[k] = (a[k] + lucas(n, i, b[k]) % b[k]) % b[k];
if(i * i != n) a[k] = (a[k] + lucas(n, n / i, b[k]) % b[k]) % b[k];
}
}
crt(); Print(power(G, ans, mod + 1));
}
/*----------------------------------------------------------*/
signed main() {Main(); return 0;}