数论基础
芝士
费马小定理
若 (p) 为质数 则
欧拉定理
若 (a ot p) 则
其中 (varphi(p)) 表示小于等于 (p) 中和 (p) 互质的数的个数
有
其中 (s_i) 为 (p) 的质因数
如果 (x ot 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)) ...
有
最大公约数与最小公倍数的性质
证明思路: (minmax) 反演
证明思路: (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('
')
/*----------------------------------------------------------*/
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 imes frac {varphi(n)}2)
仪仗队
求
[sum_{i = 1}^nsum_{j = 1}^n[gcd(i, j) = 1] ](1 leq n leq 4 imes 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}^nsum_{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}^ngcd(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('
')
/*----------------------------------------------------------*/
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 imes 10^5)
(m) 不一定为质数
跑路了...
对模数不是质数 将模数进行分解 转化为模 (p^k) 其中 (p) 为 (m) 的质因数
在对 (p) 取模的运算中 需要记录 (k = xp^y) 中的 (x) 与 (y)
其中 (x) 对 (p) 取模 (y) 对 (varphi(p)) 取模 最后 CRT 起来
整数分块
求
[sum_{i = 1}^nleft(lfloorfrac ni floor ight)^5 imes i ](n leq 10^9) 答案对 (10^9 + 7) 取模
(lfloorfrac ni floor) 只有 (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}^nsum_{b = 1}^ngcd(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('
')
#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;}