// // // // // // // // // // // // // //

数学基础 1

数论

前言

以下大多证明都是扯淡 没有任何正确性可言 有的只是为了做题而做题 随便看看就好

听课的时候的笔记 并不能保证正确性(毕竟听课效率...) 不能保证可读性(毕竟思路混乱...) 其实我还是比较适合抄课件

裸筛

for(int i = 2; i * i <= maxn; i++) if(!a[i])
	for(int j = i << 1; j <= maxn; j += i) a[j] = 0;

$O(n loglogn) $

线筛

线性筛中 每个数只会被它最小的质因子筛去

for(int i = 2; i <= maxn; i++)
{
	if(!vis[i]) p[++cnt] = i;
	for(int j = 1; j <= cnt && p[j] * i <= maxn; j++)
	{
		vis[p[j] * i] = 1;
		if(i % p[j] == 0) break;
	}
}

同余

模意义下 加减乘的直接计算不会影响最终结果取模答案

\(P4942\)

\(10^n \equiv 1 ^ n \equiv 1\)

相当于将每一位拆开 再按原数拼起来

/*
  Time: 3.31
  Worker: Blank_space
  Source: P4942 小凯的数字
*/
/*--------------------------------------------*/
#include<cstdio>
#define int long long
/*--------------------------------------头文件*/
const int A = 1e4 + 7;
const int B = 1e5 + 7;
const int C = 1e6 + 7;
const int D = 1e7 + 7;
const int mod = 1e9 + 7;
const int INF = 0x3f3f3f3f;
/*------------------------------------常量定义*/
int T, l, r;
/*------------------------------------变量定义*/
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;
}
/*----------------------------------------快读*/

/*----------------------------------------函数*/
signed main() {
	T = read(); while(T--) {l = read(); r = read(); printf("%lld\n", ((l + r) & 1) ? (r - l + 1 >> 1) % 9 * (l + r) % 9 : (l + r >> 1) % 9 * (r - l + 1) % 9);}
	return 0;
}

\(euclid\) (欧几里得)扩展

用于解 \(ax + by = \gcd(a, b)\)

性质: 对于整数 a, b 有无穷解

\(d = ax + by = a(x + \frac{b}{d} * u) + b(y - \frac{a}{d} * u)\)

求任意一组解

原理: 辗转相除

void exgcd(int a, int b, int &d, int &x, int &y) {
	if(b) exgcd(b, a % b, d, y, x), y -= x * (a / b);
	else d = a, x = 1, y = 0;
}

\(P1516\)

求:

\[mt + x \equiv nt + y(\mod l) \]

变形

\[(m - n)t \equiv y - x (\mod l) \]

等价于求解关于 \(t\)\(s\) 的不定方程

\[(m - n)t + ls = y - x \]

将一个同余方程转化为欧几里得方程

解这个方程

\[d = \gcd(n - m, l) \]

求:

\[(m - n)t + ls = d \]

如果 \(d\) 不整除 \(y - x\) 说明方程 \((m - n)t \equiv y - x(mod\ l)\) 无解

否则将 \(t\)\(s\) 扩大 \(\frac{y - x}d\) 倍 可得 \((m - n)t' + ls' = y - x\)

\(\frac ld\) 调整 \(t'\)

/*
  Time: 3.31
  Worker: Blank_space
  Source: P1516 青蛙的约会
*/
/*--------------------------------------------*/
#include<cstdio>
#include<cstring>
#define int long long
#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 = 1e4 + 7;
const int B = 1e5 + 7;
const int C = 1e6 + 7;
const int D = 1e7 + 7;
const int mod = 1e9 + 7;
const int INF = 0x3f3f3f3f;
/*------------------------------------常量定义*/
int d, t, s;
/*------------------------------------变量定义*/
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 exgcd(int a, int b, int &d, int &x, int &y) {
	if(b) exgcd(b, a % b, d, y, x), y -= x * (a / b);
	else d = a, x = 1, y = 0;
}
/*----------------------------------------函数*/
signed main() {
	int x = read(), y = read(), m = read(), n = read(), l = read();
	if(m < n) Swap(m, n), Swap(x, y);
	exgcd(m - n, l, d, t, s); int mod = l / d;
	if((y - x) % d) puts("Impossible");
	else printf("%lld", ((y - x) / d * t % mod + mod) % mod);
	return 0;
}

同余运算法则

取模运算 加减乘不变

同余运算 左右移项不变 乘一个东西不变 同时加减一个东西不变

\(P3951\)

设方程 \(ax + by = c\) 的通解是

\[\begin{cases} x = x_0 + bt \\ y = y_0 - at\end{cases} \]

由一组特解构成通解 通过调节 \(t\) 找到一组特解 \((x_1, y_1)\) 使 \(0 \leq x_1 \leq b - 1\)

\(x\) 不能为负 也不能使得 \(y\) 为负

\(y > 0\) 有正整数解

\(y < 0\) 无正整数解

找无正整数解的情况 限定 \(y_1 \leq -1\) 那么 \(c = ax_1 + by_1 \leq ax_1 - b \leq ab - a - b\) (已经限定 \(0 \leq x_1 \leq b - 1\))

证明 \(ax + by = ab - a - b\) 没有非负整数解

反证:

若存在满足条件的 \((x_0, y_0)\)\(a(x_0 + 1) + b(y_0 + 1) = ab\)

说明 \(a \mid b(y_0 + 1)\)\(a \mid y_0 + 1\) (\(a\)\(b\) 互质) 则有 \(a \leq y_0 + 1\)

同理: \(b \leq x_0 + 1\)

那么 \(a(x_0 + 1) + b(y_0 + 1) \geq 2ab > ab\)

\(\therefore ax + by = ab - a - b\) 没有非负整数解

前面证明 大于 \(ab - a - b\) 的都可以被表示 后面证明 \(ab - a - b\) 刚好不能被表示

中国剩余定理(CRT)

又称孙子定理

解一坨同余方程组

解线性不定模方程组

\(P4777\)

标准的中剰定理 规定 \(m_i\) 两两互质 但这里没有 所以是扩展(\(EXCRT\))

从两个方程开始

考虑合并

\(x \equiv a_1 (\mod m_1)\) \(x \equiv a_2(\mod m_2)\)

\(m = lcm(m_1, m_2)\) \(x \equiv a(\mod m)\)

显然有 \(a = um_1 + a_1 = vm_2 + a_2\)

\(a\)

\(um_1 - vm_2 = a_2 - a_1\)

可以求得 \(um_1 - vm_2 = 1\) 的解

在扩大到 \(a_2 - a_1\)

实现

exgcd(m1, m2, d, u, v);//d 是最小公约数
m2 = m1 / d * m2;//最小公倍数
u = (a2 - a1) / d * u % m2;//调整u
a2 = (u * m1 % m2 + a1) % m2;//根据上面的式子求a2

将方程组合并成了一个

另一种说法

\[x \equiv a \mod m \\ x = um + a \]

\[x \equiv A \mod M\\ x = vM + A \]

\[T = [m, M] \]

\[x = um + a + kT \mod m\\ x = vM + A + kT \mod M \]

\[x = S + kT \]

\[x \equiv S \mod T \]

也就是将两个方程组合并成了一个方程

其模数就是 \([m_1, m_2]\)

所以说 上面在扯淡

\[S = um + a = vM + A\\ um - vM = A - a \]

可求的是

\[um + vM = (m, M) \]

通过这个解出一个 \(u\)\(v\)

如果

\[\gcd(m, M) \mid (A - a) \]

\[u' = u\frac{A - a}{\gcd(m, M)} \\ v' = -v\frac{A - a}{\gcd(m, M)} \]

解释:

\[\begin{align} & \because \gcd(m, M) \mid (A - a)\\ & \therefore (A - a) = K \times \gcd(m, M)\\ & \therefore K = \frac{A - a}{\gcd(m, M)}\\ & \because u'm - v'M = (A - a)\\ & \therefore u'm - v'M = K \times \gcd(m, M)\\ & \therefore \frac{u'}Km - \frac{v'}KM = \gcd(m, M)\\ & \because um + vM = \gcd(m, M)\\ & \therefore u' = uK, v' = vK\\ & \therefore u' = u\frac{A - a}{\gcd(m, M)}, v' = -v\frac{A - a}{\gcd(m, M)} \end{align} \]

所以这是一个很显然的结论

/*
  Time: 3.31
  Worker: Blank_space
  Source: P4777 【模板】扩展中国剩余定理(EXCRT)
*/
/*--------------------------------------------*/
#include<cstdio>
#include<cstring>
#define int long long
#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 B = 1e5 + 7;
const int C = 1e6 + 7;
const int D = 1e7 + 7;
const int mod = 1e9 + 7;
const int INF = 0x3f3f3f3f;
/*------------------------------------常量定义*/

/*------------------------------------变量定义*/
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 exgcd(int a, int b, int &d, int &x, int &y) {
	if(b) exgcd(b, a % b, d, y, x), y -= x * (a / b);
	else d = a, x = 1, y = 0;
}
int mul(int a, int b, int p) {
	int res = 0;
	while(b)
	{
		if(b & 1) res = (res + a) % p;
		a = (a << 1) % p;
		b >>= 1;
	}
	return res;
}
/*----------------------------------------函数*/
signed main() {
    int n = read() - 1, M = read(), A = read();
    while(n--)
    {
        int m = read(), a = read(), u, v, d;
		exgcd(M, m, d, u, v);
        u = mul(u, ((a - A) % m + m) % m / d, m);
        A += M * u; M = M / d * m; A = (A + M) % M;
    }
    printf("%lld", A);
    return 0;
}

Strange Way to Express Integers

(一道扩展中剩的模板题)

/*
  Time: 4.9
  Worker: Blank_space
  Source: #10213. 「一本通 6.4 例 5」Strange Way to Express Integers
*/
/*--------------------------------------------*/
#include<cstdio>
#include<cstring>
#define int long long
#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 B = 1e5 + 7;
const int C = 1e6 + 7;
const int D = 1e7 + 7;
const int mod = 1e9 + 7;
const int INF = 0x3f3f3f3f;
/*------------------------------------常量定义*/

/*------------------------------------变量定义*/
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 exgcd(int a, int b, int &d, int &x, int &y) {
	if(b) exgcd(b, a % b, d, y, x), y -= x * (a / b);
	else x = 1, y = 0, d = a;
}
void EXCRT(int n) {
	int M = read(), A = read(); bool flag = 0;
	for(int i = 1; i <= n; i++)
	{
		int m = read(), a = read(), x, y, d;
		a = ((a - A) % m + m) % m; exgcd(M, m, d, x, y);
		if(a % d || flag) {flag = 1; continue;}
		x = (x * a / d % m + m) % m;
		A += x * M; M = M / d * m; A = (A + M) % M;
	}
	if(flag) puts("-1"); else printf("%lld\n", A);
}
/*----------------------------------------函数*/
signed main() {
	int n; while(~scanf("%lld", &n)) EXCRT(--n);
	return 0;
}

\(P3868\)

\[\begin{align} & b_i \mid n - a_i\\ & n \equiv a_i \mod b_i \end{align} \]

裸的 \(CRT\)

把上一个题的板子粘过来

/*
  Time: 4.9
  Worker: Blank_space
  Source: P3868 [TJOI2009]猜数字
*/
/*--------------------------------------------*/
#include<cstdio>
#include<cstring>
#define int long long
#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 = 1e4 + 7;
const int B = 1e5 + 7;
const int C = 1e6 + 7;
const int D = 1e7 + 7;
const int mod = 1e9 + 7;
const int INF = 0x3f3f3f3f;
/*------------------------------------常量定义*/
int n, a[12], b[12], m = 1, 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 exgcd(int a, int b, int &d, int &x, int &y) {
	if(b) exgcd(b, a % b, d, y, x), y -= x * (a / b);
	else x = 1, y = 0, d = a;
}
int mul(int a, int b, int p, int res = 0) {
	for(; b; b >>= 1, a = (a + a) % p) if(b & 1) res = (res + a) % p;
	return res;
}
/*----------------------------------------函数*/
signed main() {
	n = read();
	for(int i = 1; i <= n; i++) a[i] = read();
	for(int i = 1; i <= n; i++) b[i] = read(), m *= b[i], a[i] = (a[i] % b[i] + b[i]) % b[i];
	for(int i = 1; i <= n; i++)
	{
		int _m = m / b[i], d, x, y; exgcd(_m, b[i], d, x, y); x = (x % b[i] + b[i]) % b[i];
		ans = ((ans + mul(mul(_m, x, m), a[i], m)) % m + m) % m;
	}
	printf("%lld", (ans % m + m) % m);
	return 0;
}

\(CRT\)

解方程组 \(x \equiv a_i \mod m_i\)

\[M = \prod^n_{i = 1}m_i,\ M_i = \frac M{m_i},\ t_i,\ t_iM_i \equiv 1 (\mod m_i) \]

那么可以直接给出模 \(M\) 意义下的唯一解

\[x = \sum_{j = 1}^na_jt_jM_j \mod m_i \]

将这一坨东西带到上面的方程中

\(i \neq j\)

\[m_i \mid M_j \]

\[x \mod m_i = 0 \]

\(i = j\)

\[x \mod m_i = a_j \]

所以

\[x = \sum_{j = 1}^na_jt_jM_j (\mod m_i) = a_j \]

\(P1495\)

/*
  Time: 4.9
  Worker: Blank_space
  Source: P1495 【模板】中国剩余定理(CRT)
*/
/*--------------------------------------------*/
#include<cstdio>
#include<cstring>
#define int long long
#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 = 1e4 + 7;
const int B = 1e5 + 7;
const int C = 1e6 + 7;
const int D = 1e7 + 7;
const int mod = 1e9 + 7;
const int INF = 0x3f3f3f3f;
/*------------------------------------常量定义*/
int n, a[12], b[12], m = 1, 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 exgcd(int a, int b, int &d, int &x, int &y) {
	if(b) exgcd(b, a % b, d, y, x), y -= x * (a / b);
	else x = 1, y = 0, d = a;
}
/*----------------------------------------函数*/
signed main() {
	n = read();
	for(int i = 1; i <= n; i++) a[i] = read(), b[i] = read(), m *= a[i];
	for(int i = 1; i <= n; i++)
	{
		int _m = m / a[i], d, x, y; exgcd(_m, a[i], d, x, y);
		ans = ((ans + _m * x * b[i]) % m + m) % m;
	}
	printf("%lld", ans);
	return 0;
}

欧拉函数

欧拉函数 \(\varphi(m)\) 表示不超过 \(m\) 且和 \(m\) 互素的整数的个数 (定义\(\varphi(1) = 1\))

计算

给出 \(m\) 的唯一分解式

\[m = \prod p_i^{a_i} \]

\[\varphi(m) = m\prod\left(1 - \frac{1}{p_i}\right) \]

欧拉函数的一个性质(欧拉定理)

\(m > 1\) 为整数 \(a\) 是与 \(m\) 互素的任一整数 则

\[a^{\varphi(m)} \equiv 1 (\mod m) \]

欧拉定理的证明

把不超过 \(m\) 的且与 \(m\) 互质的正整数取出构成集合 \(\{x_1, x_2, ..., x_{\varphi (m)}\}\)

\(a\)\(m\) 互质 易证集合 \(\{ax_1, ax_2, ..., ax_{\varphi (m)}\}\) 在模 \(m\) 意义下与前一集合相等 (均在模 \(m\) 意义下的缩系)

将集合内所有原数相乘 得到:

\[ax_1 \times ax_2 \times ax_{\varphi (m)} \equiv a^{\varphi (m)}\prod^{\varphi(m)}_{i = 1}x_i \equiv \prod_{i = 1}^{\varphi(m)}x_i(\mod m) \]

整理得(把右边的减过来):

\[m \mid (a^{\varphi(m)} - 1)\prod_{i = 1}^{\varphi(m)}x_i \]

因为 \(\prod_{i = 1}^{\varphi(m)}x_i\)\(m\) 互质 有:

\[m \mid a^{\varphi(m)} - 1 \]

即:

\[a^{\varphi(m)} \equiv 1 (\mod m) \]

欧拉函数筛

\[\varphi(m) = m\prod\left(1 - \frac 1{p_i}\right) \]

由公式可以推知 \(m\) 的每个质因子 \(p\) 会对 \(m\) 产生贡献

相当于每个质数会对它的倍数产生贡献

\(O(nlogn)\) 进行统计

for(int i = 1; i <= n; i++) e[i] = i;
for(int i = 2; i <= n; i++) if(e[i] == i)
    for(int j = i; j <= n; j += i) e[j] = e[j] / i * (i - 1);

欧拉线筛

\(O(n)\) 线筛

线筛的原理是每个数只有其最小的质因数筛去

它也可以 \(O(n)\) 筛除所有积性函数值

分两类讨论

代码

phi[1] = 1;
for(int i = 2; i < maxn; i++)
{
    if(!vis[i]) pri[++cnt] = i, phi[i] = i - 1;//1
    for(int j = 1; j <= cnt && pri[j] * i < maxn; j++)
    {
        vis[pri[j] * i] = 1;
        phi[pri[j] * i] = (pri[j] - 1) * phi[i];//2
        if(!(i % pri[j])) {phi[pri[j] * i] = pri[j] * phi[i]; brea$k;}//3
    }
}

代码解释

//1

这里的 \(i\) 是一个质数 \(\varphi(i) = i - 1\)

//2

\(i\) 不是质数 \(pri[j]\) 是其一个质因子 因为欧拉函数是一个积性函数 所以

\[\varphi(i \times pri[j]) = \varphi(pri[j]) \times \varphi(i) \]

\(pri[j]\) 又是一个质数 所以

\[\varphi(pri[j]) = pri[j] - 1 \]

所以就有

\[\varphi(pri[j] * i) = (pri[j] - 1) \times \varphi(i) \]

//3

\(\varphi(pri[j] \times i) = pri[j] \times \varphi(i)\) 这个玩意儿就比较苟了

设:

\[i \times pri[j] = x \times pri[j]^{n - 1} \times pri[j] = x \times pri[j]^n \]

\(x\)\(pri[j]\) 互质

\(p\) 为质数 有:

\[\varphi(p^k) = p^k - p^{k - 1} = p^{k - 1} \times (p - 1) \]

证明:

\(p^k\) 中有 \(p^k\) 个数 其只有 \(p\) 一个质因子 所以其所有的因数都是 \(p\) 的倍数 所以其包含自己在内的因子有 \(p^{k - 1}\) 个 把因子去掉 就有了上式

所以 有:

\[\varphi(p^k) = p^{k - 1} \times (p - 1)\\ \varphi(p^{k - 1}) = p^{k - 2} \times (p - 1) \]

所以 有:

\[\varphi(p^k) = p \times p^{k - 2} \times (p - 1) \\= \varphi(p^{k - 1}) \times p \]

结合上面设的东西 有:

\[\varphi(i \times pri[j]) = \varphi(x \times pri[j]^n) \\=\varphi(x) \times \varphi(pri[j]^n)\\=\varphi(x) \times \varphi(pri[j]^{n - 1} ) \times pri[j] \]

且:

\[\begin{align} & \because i \times pri[j] = x \times pri[j] ^ {n - 1} \times pri[j]\\ & \therefore x \times pri[j] ^ {n - 1} = i\\ & \therefore \varphi(x) \times \varphi(pri[j]^{n - 1}) = \varphi(x \times pri[j]^{n - 1})= \varphi(i)\\ & \therefore \varphi(x) \times \varphi(pri[j]^{n - 1}) \times pri[j] = \varphi(i) \times pri[j]\\ & \therefore \varphi(i \times pri[j]) = \varphi(i) \times pri[j] \end{align} \]

结合上面这三点 就可以处理欧拉函数了

即(定义 \(p\) 为质数):

  1. \(\varphi(1) = 1\)
  2. \(\varphi(p) = p - 1\)
  3. \((p, x) = 1\)\(\varphi(p \times x) = \varphi(p) \times \varphi(x)\)
  4. \(p \mid x\)\(\varphi(x \times p) = \varphi(x) \times (p - 1)\)

这也是线筛筛欧拉函数的原理

\(P2158\)

遮住一半

\(n + 1\) 列的贡献是 \(\varphi(i)\)

最终答案就是 \(1 + 2\sum_{i = 1}^{n - 1}\varphi(i)\)

裸筛

/*
  Time: 4.11
  Worker: Blank_space
  Source: P2158 [SDOI2008]仪仗队
*/
/*--------------------------------------------*/
#include<cstdio>
#include<cstring>
#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 = 1e4 + 7;
const int B = 1e5 + 7;
const int C = 1e6 + 7;
const int D = 1e7 + 7;
const int mod = 1e9 + 7;
const int INF = 0x3f3f3f3f;
/*------------------------------------常量定义*/
int n, e[B], ans = 1;
/*------------------------------------变量定义*/
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 main() {
	n = read(); if(n == 1) {puts("0"); return 0;}
	for(int i = 1; i <= n; i++) e[i] = i;
	for(int i = 2; i <= n; i++) if(e[i] == i)
		for(int j = i; j <= n; j += i) e[j] = e[j] / i * (i - 1);
	for(int i = 1; i < n; i++) ans += e[i] << 1;
	printf("%d", ans);
	return 0;
}

线筛

/*
  Time: 4.11
  Worker: Blank_space
  Source: P2158 [SDOI2008]仪仗队
*/
/*--------------------------------------------*/
#include<cstdio>
#include<cstring>
#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 = 1e4 + 7;
const int B = 1e5 + 7;
const int C = 1e6 + 7;
const int D = 1e7 + 7;
const int mod = 1e9 + 7;
const int INF = 0x3f3f3f3f;
/*------------------------------------常量定义*/
int n, phi[B], pri[B], cnt, ans;
bool vis[B];
/*------------------------------------变量定义*/
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 main() {
	n = read(); if(n == 1) {puts("0"); return 0;} phi[1] = 1;
	for(int i = 2; i <= n; i++)
	{
		if(!vis[i]) pri[++cnt] = i, phi[i] = i - 1;
		for(int j = 1; j <= cnt && i * pri[j] <= n; j++)
		{
			vis[pri[j] * i] = 1; phi[pri[j] * i] = (pri[j] - 1) * phi[i];
			if(!(i % pri[j])) {phi[pri[j] * i] = pri[j] * phi[i]; break;}
		}
	}
	for(int i = 1; i < n; i++) ans += phi[i];
	printf("%d", ans << 1 | 1);
	return 0;
}

乘法逆元

鸽子合集

模数为质数 费马小定理

\[a^{p - 1} \equiv 1 \mod p \]

\[\frac xy \equiv x \times y^{p - 2} \mod p \]

其他:

\(O(n)\) 递推

inv[1] = 1;
inv[i] = (mod - mod / i) * inv[i % mod] % mod;

证明

设:

\[p = k \times i + r\ (r < i) \]

有:

\[k \times i + r \equiv 0 \mod p \]

两边同乘 \(i^{-1}r^{-1}\) 有:

\[k \times r^{-1} + i^{-1} \equiv 0 \mod p \]

即:

\[i^{-1} \equiv -k \times r^{-1} \equiv -\lfloor\frac pi \rfloor \times r^{-1} \mod p \]

证毕

卡特兰数

\[h_n = \sum_{i = 0}^{n - 1}h_i \times h_{n - 1 - i} = {2n \choose n} - {2n \choose n + 1} = \frac{{2n \choose n}}{n + 1} = h_{n - 1}\frac{4n - 2}{n + 1}\ (h_0 = 1) \]

题目模型

  1. \(n + 2\) 边形剖分数
  2. \(n\) 对合法括号方案数
  3. \(n\) 个元素出栈方案数
  4. \(n\) 个节点的二叉树个数
  5. \(n \times n\) 的放个从左下到右上不穿过对角线的方案数
  6. 圆周上 \(2n\) 个顶点配对相连 线段两两不想交方案数
  7. \(2n\) 个人 一半带了 \(5\) 元 另一半带了 \(10\) 元 票价 \(5\) 元 不利用外界的前找完所有人的前的站队方案数
  8. ... ...

\(n + 2\) 边形剖分计数

\(n\) 条连接顶点的线段把凸 \(n + 2\) 边形剖分成 \(n\) 个三角形的方案数

人为定义 \(h_0 = 1\)

\(n + 2\) 个顶点标号 (\(0 - n + 1\))

考虑一个方案中与连接 \(0\) 号点相连的最小的点 \(i\)

\(0 - i\) 线剖分了多少凸四边形

一边不连 \(0\) 号点 方案数为 \(h_{i - 2}\) 另一侧可以连 \(0\) 号点 方案数是 \(h_{n - i + 1}\)

总方案

\[h_n = \sum_{i = 2}^{n + 1}h_{i - 2}h_{n - i + 1} = \sum_{i = 0}^{n - 1}h_ih_{n - i - 1} \]

合法括号计数

\(n\) 对括号可以组成多少合法括号串

人为定义 \(h_0 = 1\)

\(n\) 对括号任意放置的方案数为 \({2n \choose n}\)

考虑非法括号方案 找到第一个非法位置 把该位置后面的括号取反

得到一个有 \(n + 1\) 个右括号的序列 方案数为 \({2n \choose n + 1}\) 每一个 \(n + 1\) 个右括号的序列都对应了一个非法的原序列

得到

\[h_n = {2n \choose n} - {2n \choose n + 1} = \sum_{i = 0}^{n - 1}h_i \times h_{n - i - 1} \]

化简

\[{2n \choose n} - {2n \choose n + 1} = \frac{(2n)!}{n!n!} - \frac{(2n)!}{(n + 1)!(n - 1)!} \\ = \frac{(2n)!}{n!n!}\left(1 - \frac{n}{n + 1}\right)\\ = \frac{{2n \choose n}}{n + 1} \]

故 有

\[\frac{h_n}{h_{n - 1}} = \frac{(2n)!}{(n + 1)n!n!} \times \frac{n(n - 1)!(n - 1)!}{(2n - 2)!} \\=\frac{2n(2n - 1)n}{(n + 1)n^2} \\= \frac{4n - 2}{n + 1} \]

\[h_n = {2n \choose n} - {2n \choose n + 1} = \frac{{2n \choose n}}{n + 1} = h_{n - 1}\frac{4n - 2}{n + 1} \]

其他

\(n\) 个元素出栈方案数

入栈 = 左括号

出栈 = 右括号

\(n \times n\) 的方格从左下到右上不穿过对角线的方案数

右 = 左括号

上 = 右括号

圆周上 \(2n\) 个顶点配对相连 线段两两不相交方案数

\(2n\) 个顶点标号

连向点标号比自己大的点 = 左括号

连向点标号比自己小的点 = 右括号

\(2n\) 个人 一半带了 \(5\) 元 另一半带了 \(10\) 元 票价 \(5\) 元 不利用外界的前找完所有人的前的站队方案数

\(5\) 元 = 左括号

\(10\) 元 = 右括号

二叉树计数

人为定义 \(h_0 = 1\)

只考虑根节点 若左子树中有 \(i\) (\(0 \leq i \leq n - 1\)) 个点 则右子树有 \(n - i - 1\) 个点

\[h_n = \sum_{i = 0}^{n - 1}h_i\times h_{n - 1 - i} \]

P1044 栈

\(n \leq 18\) ???

\(n \leq 5000\) \(h_n = \sum_{i = 0}^{n - 1}h_i \times h_{n - 1 - i}\)

\(n \leq 10 ^ 5\ \ h_n = h_{n - 1}\frac{4n - 2}{n + 1}\)

\(n \leq 10^9\ \ h_n = {2n \choose n} - {2n \choose n + 1}\) , \(Lucas\)

话说 \(Lucas\) 是个啥

康托展开

康托展开是全排列序列到自然数的一一映射 该自然数可以代表该序列在全排列中的排名

(正) 康托展开 : 全排列 \(\to\) 名次

考虑有多少个排列可以比当前排列小

(逆) 康托展开 : 名次 \(\to\) 全排列

可以快速知道每一位 后面比当前小的数的个数

\(P2525\)

(菜到只配做入门题)

先康了

然后 \(-1\)

再逆康

/*
  Time: 4.11
  Worker: Blank_space
  Source: P2525 Uim的情人节礼物·其之壱
*/
/*--------------------------------------------*/
#include<cstdio>
#include<cstring>
#include<vector>
#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 = 1e4 + 7;
const int B = 1e5 + 7;
const int C = 1e6 + 7;
const int D = 1e7 + 7;
const int mod = 1e9 + 7;
const int INF = 0x3f3f3f3f;
/*------------------------------------常量定义*/
int n, m, f[12], _a[12], a[12];
/*------------------------------------变量定义*/
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 cantor() {
	int res = 0, x;
	for(int i = 0; i < n; i++)
	{
		x = 0;
		for(int j = i + 1; j < n; j++) if(_a[i] - _a[j] > 0) x++;
		res += x * f[n - i - 1];
	}
	return res;
}
int incantor(int k) {
	int x; std::vector <int> v;
	while(!v.empty()) v.pop_back();
	for(int i = 1; i <= n; i++) v.push_back(i);
	for(int i = 1; i < n; i++)
	{
		a[i] = v[k / f[n - i]]; v.erase(v.begin() + k / f[n - i]);
		k %= f[n - i];
	}
	a[n] = v[0];
}
/*----------------------------------------函数*/
int main() {
	n = read(); f[1] = 1;
	for(int i = 0; i < n; i++) _a[i] = read();
	for(int i = 2; i <= 10; i++) f[i] = f[i - 1] * i;
	int t = cantor(); if(!t) puts("ERROR");
	incantor(t - 1);
	for(int i = 1; i <= n; i++) printf("%d ", a[i]);
	return 0;
}

\(P5367\) 【模板】康托展开

\(n^2\) 被卡了

发现多出来的那个 \(O(n)\) 就是一个找还没有出现过的 \(k\) 大值的过程 随便写个数据结构

像什么权值树状数组 权值线段树 平衡.. 没事了

/*
  Time: 4.11
  Worker: Blank_space
  Source: P5367 【模板】康托展开
*/
/*--------------------------------------------*/
#include<cstdio>
#include<cstring>
#define lowbit(x) ((x) & -(x))
#define int long long
#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 = 1e4 + 7;
const int B = 1e5 + 7;
const int C = 1e6 + 7;
const int D = 1e7 + 7;
const int mod = 998244353;
const int INF = 0x3f3f3f3f;
/*------------------------------------常量定义*/
int n, a[C], ans, f[C], t[C];
/*------------------------------------变量定义*/
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 add(int x, int k) {for(int i = x; i <= n; i += lowbit(i)) t[i] += k;}
int sum(int x, int res = 0) {for(int i = x; i; i -= lowbit(i)) res += t[i]; return res;}
/*----------------------------------------函数*/
signed main() {
	n = read(); f[0] = 1;
	for(int i = 1; i <= n; i++) f[i] = f[i - 1] * i % mod, add(i, 1);
	for(int i = 1; i <= n; i++)
	{
		a[i] = read();
		ans = (ans + (sum(a[i]) - 1) * f[n - i] % mod) % mod;
		add(a[i], -1);
	}
	printf("%lld", ans + 1);
	return 0;
}

简单计数

第一类斯特林数

\(s_{n, m}\) 表示的是将 \(n\) 个不同的元素构成 \(m\) 个圆排列的方案数

考虑插入第 \(n\) 个元素 可以直接递推

  • 自成一环 环数加一
  • 插在某个元素后面 环数不变

\[s_{n, m} = s_{n - 1, m - 1} + (n - 1) \times s_{n - 1, m} \]

注意边界

第二类斯特林数

\(s_{n, m}\) 表示的是吧 \(n\) 个不同元素划分到 \(m\) 个集合中的方案数

考虑插入第 \(n\) 个元素

  • 自成一集合 集合数加一
  • 加入某集合 集合数不变

\[s_{n, m} = s_{n - 1, m - 1} + m \times s_{n - 1, m} \]

注意边界

\[s_{0, i} = 0 \]

\(P1287\)

如果盒子相同 就是第二类斯特林数 但是盒子不同 所以需要乘上盒子的全排列

答案是

\[s_{n, r} \times r! \]

或者 \(dp\)

\[f_{i, j} = f_{i - 1, j - 1} + j \times f_{i - 1, j} \]

\[ans = f_{n, r} \times r! \]

错排计数

满足 \(a_i \ne i\ (1 \leq i\leq n)\) 的排列的个数

考虑第 \(n\) 个位置是 \(i\)

  • \(i\) 个位置上恰好是 \(n\)
  • \(i\) 个位置上不是 \(n\)\(n\) 看成 \(i\)

\[d_n = (n - 1)(d_{n - 1} + d_{n - 2})\ \ (d_1 = 0, d_2 = 1) \]

\(P4071\)

把稳定的拿出来 剩下的就是错排

\[ans = {n \choose m}d_{n - m} \]

/*
  Time: 4.11
  Worker: Blank_space
  Source: P4071 [SDOI2016]排列计数
*/
/*--------------------------------------------*/
#include<cstdio>
#include<cstring>
#define int long long
#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 = 1e4 + 7;
const int B = 1e5 + 7;
const int C = 1e6 + 7;
const int D = 1e7 + 7;
const int mod = 1e9 + 7;
const int INF = 0x3f3f3f3f;
/*------------------------------------常量定义*/
int T, n, m, d[C], f[C], inv[C], 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;
}
/*----------------------------------------快读*/
int mul(int a, int b) {int res = 1; for(; b; b >>= 1, a = a * a % mod) if(b & 1) res = res * a % mod; return res;}
/*----------------------------------------函数*/
signed main() {
	T = read(); d[2] = 1; f[1] = 1; inv[1] = 1;
	for(int i = 3; i <= C - 2; i++) d[i] = (i - 1) * (d[i - 1] + d[i - 2]) % mod;
	for(int i = 2; i <= C - 2; i++) f[i] = f[i - 1] * i % mod, inv[i] = mul(f[i], mod - 2);
	while(T--)
	{
		n = read(); m = read();
		if(n - m == 1) {puts("0"); continue;} if(m == n) {puts("1"); continue;} if(!m) {printf("%lld\n", d[n]); continue;}
		ans = f[n] * inv[m] % mod * inv[n - m] % mod * d[n - m] % mod;
		printf("%lld\n", ans);
	}
	return 0;
}

—— \(END\)

posted @ 2021-04-11 08:33  Blank_space  阅读(543)  评论(7编辑  收藏  举报
// // // // // // //