首页 > 其他分享 >数论整理

数论整理

时间:2024-06-11 22:33:55浏览次数:12  
标签:pmod frac 数论 ll times int rg 整理

1 同余

1.

若\(a \equiv b \pmod m\),当且仅当\(m \mid (a - b)\)

2.同加性:

若\(a \equiv b \pmod m\),则\(a + c \equiv b + c \pmod m\)

3.同乘性:

若\(a \equiv b \pmod m\),则\(a * c \equiv b * c \pmod m\) 若\(a \equiv b \pmod m, c \equiv d \pmod m\),则\(a * c \equiv b * d \pmod m\)

4.

不满足同除性,但若\(gcd(c, m) = 1\),则当\(a * c \equiv b * c \pmod m\)时,有\(a \equiv b \pmod m\)

5.同幂性:

若\(a \equiv b \pmod m\),则\(a ^ n \equiv b ^ n \pmod m\)

6.推论1:

\(a * b \bmod k = (a \bmod k) * (b \bmod k) \bmod k\)

7.推论2:

若\(p, q\)互质,且\(a \bmod p = x\),\(a \bmod q = x\),则\(a \bmod (p * q) = x\)

2 素数

2.1 有关素数的定理

1.唯一分解定理:任何一个大于1的正整数都能被唯一分解为有限个素数的乘积,可写作:\(N = {p_1} ^ {c_1}{p_2} ^ {c _ 2} \cdots {p _ m} ^ {c _ m}\), 其中\({c_i}\)都是正整数,\({p _ i}\)都是素数且满足\({p_1} < {p_2} < \cdots <{p_m}\)

2.N中最多只能含有一个大于\(\sqrt{N}\)的因子

3.分解质因数:试除法:枚举\(2 \sim \sqrt{n}\),遇到质因子就除净并且记录质因子的个数。如果最后\(n > 1\),这就说明这就是大于\(\sqrt{n}\)的因子

int n, a[10001];  //质因子的个数 
inline void decompose(int x) {  //分解质因数 
	for (rg int i = 2; i * i <= x; i++) {
		while (x % i == 0) {
			a[i]++;
			x /= i;
		}
	}
	if (x > 1) a[x]++;
}

2.2 素数判定

1.按照素数的定义模拟

inline bool is_prime(int x) {
	if (x == 0 || x == 1) return false;
	for (rg int i = 2; i * i <= x; i++) {
		if (x % i == 0) return false;
	}
	return true;
}

2.埃式筛法

int prime[N], cnt;
bool is_prime[N];
inline void Eratosthenes(int n) {
	is_prime[0] = is_prime[1] = false;
	for (rg int i = 2; i <= n; i++) {
		is_prime[i] = true;
	}
	for (rg int i = 2; i <= n; i++) {
		if (is_prime[i]) {
			prime[++cnt] = i;
			if ((long long)i * i > n) continue;
			for (rg int j = i * i; j <= n; j += i) {
				is_prime[j] = false;
			}
		}
	}
}

3.线性筛法(欧拉筛法):每个合数只被它最小的质因子筛掉

int vis[N];  //划掉合数
int prime[N];  //记录质数
int cnt;  //质数个数
inline void get_prime(int n) {
	for (rg int i = 2; i <= n; i++) {
		if (!vis[i]) prime[++cnt] = i;  //如果当前数没被划掉,必定是质数,记录 
		for (rg int j = 1; 1ll * i * prime[j] <= n; j++) {  //枚举已记录的质数,如果合数越界则中断 
			vis[i * prime[j]] = true;  //划掉合数 
			if (i % prime[j] == 0) break;  //控制每个合数只被它的最小素数因子筛掉一次 
		}
	}
}

2.3 欧拉函数

1.定义:\(1 \sim n\)中与\(n\)互质的数的个数,用字母\(\varphi\)表示。

\(\varphi(n) = n \times \prod _ {i = 1} ^ {k} (1 - \frac{1}{p _ i})\),其中\(p_1, p_2, \cdots, p_k\)为n的所有质因数。

2.性质:

(1)\(\varphi(1) = 1\)。

(2)若p是素数,则\(\varphi(p) = p - 1\)。

(3)若p是素数,则\(\varphi(p ^ k) = (p - 1) \times p ^ {k - 1}\)。

(4)欧拉函数为积性函数:$\forall a, b \in N^* \(,且\)gcd(a, b) = 1\(,则\)\varphi(a \times b) = \varphi(a) \times \varphi(b)\(。特别的,对于奇数n,\)\varphi(2n) = \varphi(n)$

3.欧拉函数性质变形:

(1)p为素数,若n % p \(= 0\),则\(\varphi(n \times p) = p \times \varphi(n)\)。(\(n \times p\)的素数因子和n是一样的,所以用欧拉函数公式把\(\varphi(n \times p)\)展开)

(2)p为素数,若n % p \(\ne 0\),则\(\varphi(n \times p) = (p - 1) \times \varphi(n)\)。(n和p互质,满足积性函数)

(3)与n互质的数都是成对出现的,且每对的和为n,所以大于2的数的\(\varphi(n)\)都为偶数。

4.求欧拉函数:

(1)试除法:如果只要求一个数的欧拉函数值,那么直接根据定义,在质因数分解的同时求解

inline int euler_phi(int n) {
	rg int ans = n;
	for (rg int i = 2; i * i <= n; i++) {
		if (n % i == 0) {
			ans = ans / i * (i - 1);
			while (n % i == 0) n /= i;  //把这个因数除尽
		}
	}
	if (n > 1) ans = ans / n * (n - 1);
	return ans;
}


(2)筛法:在线性筛中,每一个合数都是被最小的质因子筛掉。比如设\(p_1\)是n的最小质因子,\(i = \frac{n}{p_1}\),那么线性筛的过程中n通过\(i \times p_1\)筛掉。

如果\(i \bmod p_1 = 0\),那么i包含了n的所有质因子:

\[\begin{aligned}\varphi(n) = n \times \prod_{i= 1}^s{\frac{p_i - 1}{p_i}} = p_1 \times i \times \prod_{i = 1}^s{\frac{p_i - 1}{p_i}}& = p_1 \times \varphi(i)\end{aligned} \]

那如果\(i \bmod p_1 \ne 0\)呢,这时i和\(p_1\)是互质的,根据欧拉函数性质,我们有:

\(\begin{aligned}\varphi(n) & = \varphi(p_1) \times \varphi(i) = (p_1 - 1) \times \varphi(i) \end{aligned}\)

int p[N];  //存质数
int cnt;
int phi[N];
bool not_prime[N];  //存合数
inline void get_phi(int n) {
	phi[1] = 1;
	for (rg int i = 2; i <= n; i++) {
		if (!not_prime[i]) {  //是质数
			p[++cnt] = i;
			phi[i] = i - 1;
		}
		for (rg int j = 1; i * p[j] <= n; j++) {
			rg int m = i * p[j];
			not_prime[m] = 1;  //标记为合数
			if (i % p[j] == 0) {
				phi[m] = p[j] * phi[i];
				break;
			} else {
				phi[m] = (p[j] - 1) * phi[i];
			}
		}
	}
}

2.4 线性筛求约数个数与约数和

1.约数个数:

用\(d_i\)表示i的约数个数,\(num_i\)表示i的最小质因子出现次数。

若\(n = \prod_{i = 1}^m {p_i} ^ {c_i}\),则\(d_i = \prod_ {i = 1}^m (c_i + 1)\)。

int prime[N], cnt;
bool not_prime[N];
int d[N];  //约数个数 
int num[N];  //最小质因数的个数 
int n;
inline void get_prime(int n) {
	d[1] = 1;
	for (rg int i = 2; i <= n; i++) {
		if (!not_prime[i]) {  //i是质数 
			prime[++cnt] = i;
			d[i] = 2;  //1和i 
			num[i] = 1;  //i自己,共1个 
		}
		for (rg int j = 1; i * prime[j] <= n; j++) {
			rg int m = i * prime[j];
			not_prime[m] = true;
			if (i % prime[j] == 0) {
				d[m] = d[i] / (num[i] + 1) * (num[i] + 2);
				num[m] = num[i] + 1;
				break;
			} else {
				d[m] = d[i] * 2;
				num[m] = 1;
			}
		}
	}
}

2.约数和:\(sd_i\)表示i的约数和,\(num_i\)表示最小质因子的\(p_0 + p_1 + \cdots + p_k\)。

\(sd_i = (p_1^0 + p_2^1 + \cdots + p_1^{r_1}) * (p_2^0 + p_2^1 + \cdots + p_2^{r_2}) * \cdots* (p_k^0 + p_k^1 + \cdots + p_k ^ {r _ k})= \prod_{i = 1}^k \sum_{j = 0}^{r_k} {p_i ^ j}\)

分治求等比数列的和:\(s = a^0 + a^1 + a^2 + \cdots + a^n\)

当n为奇数时:\(s = (1 + a^{\frac{n}{2} + 1}) * (a^0 + a^1 + \cdots + a^{\frac{n}{2}})\)

当n为偶数时:\(s = (1 + a^{\frac{n}{2} + 1}) * (a^0 + a^1 + \cdots + a^{\frac{n}{2} - 1}) + a^{\frac{n}{2}}\)

int prime[N], cnt;
bool not_prime[N];
int num[N];
int sd[N];
int n;
inline void get_primes(int n) {
	sd[1] = 1;
	for (rg int i = 2; i <= n; i++) {
		if (!not_prime[i]) {
			sd[i] = num[i] = i + 1;
			prime[++cnt] = i;
		}
		for (rg int j = 1; i * prime[j] <= n; j++) {
			rg int m = i * prime[j];
			not_prime[m] = true;
			if (i % prime[j]) {
				sd[m] = sd[i] * sd[prime[j]];  //积性函数
				num[m] = prime[j] + 1; 
			} else {
				sd[m] = sd[i] / num[i] * (num[i] * prime[j] + 1);
				num[m] = num[i] * prime[j] + 1;
				break;
			}
		}
	}
}

2.5 欧拉定理/费马小定理

1.费马小定理:若p为素数,\(\gcd(a, p) = 1\),则\(a^{p - 1} \equiv 1 \pmod p\)。

另:对于任意整数a,有\(a ^ p \equiv a \pmod p\)。

2.欧拉定理:若\(gcd(a, m) = 1\),则\(a^{\varphi(m)} \equiv 1 \pmod m\)。

3.扩展欧拉定理:

\(a^b \equiv \begin{cases} a^{b \bmod \varphi(m)}, &\gcd(a,m) = 1, \\ a^b, &\gcd(a,m)\ne 1, b < \varphi(m), \\ a^{(b \bmod \varphi(m)) + \varphi(m)}, &\gcd(a,m)\ne 1, b \ge \varphi(m). \end{cases} \pmod m\)

4.秦九昭算法:

\[\begin{aligned} f(x) &= a _ n x ^ n + a _ {n - 1} x ^ {n - 1} + \cdots + a _ 1 x + a_0 \\ &= (a_n x^{n - 1} + a_{n - 1} x^{n - 2} + \cdots + a_2 x + a_1)x + a_0 \\ &= ((a_n x ^ {n - 2} + a_{n - 1} x ^ {n - 3} + \cdots + a _ 3 x + a _ 2) x + a_1) x + a_0 \\ &\vdots \\ &= (\cdots((a_n x + a _ {n - 1}) x + a _ {n - 2} ) x + \cdots + a_1)x + a_0 \end{aligned} \]

求多项式的值时,首先计算最内层括号内一次多项式的值,即:

\[\begin{aligned} v_0 = a_n \\ v_1 = a _ n &x + a _ {n - 1} \end{aligned} \]

然后由内向外逐层计算一次多项式的值,即:

\[\begin{aligned} v_2 = v_1 x + a_{n - 1} \\ v_3 = v_2 x + a_{n - 3} \\ \vdots \\ v_n = v_{n - 1} x + a_0 \end{aligned} \]

这样,求n次多项式\(f(x)\)的值就转化为求n个一次多项式的值。

3 最大公约数

3.1 欧几里得算法

1.辗转相除法:\(gcd(x, y) = gcd(y, x \% y)\)。

//递归形式 
inline int gcd(int x, int y) {
	return (y == 0 ? x : gcd(y, x % y));
}
//非递归形式 
inline int gcd(int x, int y) {
	rg int r = x % y;
	while (r) {
		x = y;
		y = r;
		r = x % y;
	}
	return y;
}

(速度:非递归 \(>\) 递归 \(>\) __gcd(),\(10^7\)数据下前两个差距不大,__gcd()慢0.1s左右)

2.最小公倍数:\(lcm(a, b) * gcd(a, b) = a * b\)

3.裴蜀定理(贝祖定理):设\(a, b\)为不全为0的整数,则\(\exists x, y\),使得:\(ax + by = gcd(a, b)\)。

4.裴蜀定理推广:

(1)一定存在整数x, y,满足\(ax + by = gcd(x, y) \times n\)。

(2)一定存在整数\(X_1 , X_2, \cdots , X_n\),满足\(\sum _ {i = 1} ^ n A_iX_i = gcd(A_1, A_2, \cdots , A_n)\)。

3.2 扩展欧几里得

1.作用:求\(ax + by = gcd(a, b)\)的一组整数解。

2.证明:

\(ax_1 + by_1 = gcd(a, b)\)

\(bx_2 + (a \% b)y_2 = gcd(b, a \% b)\)

\(\because gcd(a, b) = gcd(b, a \% b)\)

\(\therefore ax_1 + by_1 = bx_2 + (a \% b)y_2\)

\(\because a \% b = a - \lfloor \frac a b \rfloor \times b\)

\(\therefore ax_1 + by_1 = bx_2 + (a - \lfloor \frac a b \rfloor \times b)y_2 = ay_2 + b(x_2 - \lfloor \frac a b \rfloor y_2)\)

因为系数相同,所以我们可以让\(x_1 = y_2\),\(y_1 = x_2 - \lfloor \frac a b \rfloor y_2\)

显然,可以利用递归,先求出下一层的解\(x_2\),\(y_2\),再回到上一层,求出特解\((x_1, y_1)\),顺便求出\(gcd(a, b)\)。

//求解 ax+by = gcd(a,b) 
//返回gcd(a,b)的值
inline int exgcd(int a, int b, int &x, int &y) {
	if (b == 0) {
		x = 1;
		y = 0;
		return a;
	}
	int ret = exgcd(b, a % b, x, y);
	int t = x;
	x = y;
	y = t - a / b * y;
	return ret;
}

3.构造通解:

\[\begin{cases}x = x_0 + \frac b {gcd(a, b)} \times k \\ y = y_0 - \frac a {gcd(a, b)} \times k\end{cases} \]

4.求不定方程\(ax + by = c\)的一组整数解:

若\(gcd(a, b) | c\),则有整数解。先用扩欧求得\(ax+by = gcd(a, b)\)的解,再乘以\(\frac c {gcd(a, b)}\),即原方程的特解。

若\(gcd(a, b) \nmid c\),则无整数解。

inline int exgcd(int a, int b, int &x, int &y) {
	if (b == 0) {
		x = 1;
		y = 0;
		return a;
	}
	rg int ret = exgcd(b, a % b, x, y);
	rg int t = x;
	x = y;
	y = t - a / b * y;
	return ret;
}
int main() {
	int a, b, c, x, y;
	cin >> a >> b >> c;
	rg int d = exgcd(a, b, x, y);
	if (c % d == 0) {
		cout << c / d * x << " " << c / d * y << "\n";
	} else {
		cout << "none" << "\n";
	}
	return qwq;
}

5.扩欧求解线性同余方程:

(1)把同余方程转化为不定方程:由\(ax \equiv b \pmod m\)得\(ax = -my + b\),即\(ax + my = b\),由裴蜀定理得,当\(gcd(a, m) \mid b\)时有解

(2)扩欧求\(ax + my = gcd(a, m)\)的解,之后把x乘以\(\frac b {gcd(a, m)}\),即原始方程的特解。

(3)x的最小整数解:\(x = (x \% m + m) \% m\)

4 乘法逆元

4.1 定义

如果一个线性同余方程\(ax \equiv 1 \pmod b\),则称x为a在模b意义下的乘法逆元,记作\(a^{-1}\)。

并非所有情况下都存在乘法逆元,但当\(gcd(a, b) = 1\),即a,b互质时,存在乘法逆元。

4.2 逆元的用途

因为对于除法取模不成立,即\((a \div b) \% p \ne ((a \% p) \div (b \% p)) \% p\),所以用逆元来解决这个问题。

逆元就是把除法取模运算转化为乘法取模运算。

对于\((a \div b) \% p = m\),假设存在一个数x满足\((a \times x) \% p = m\),两边同乘b得到:\(a \% p = (m \times (b \% p)) \% p\);若a和b均小于p的话,上式改为:\(a = (m \times b) \% p\),等式两边同时乘以x,联立(2)式得到:\((a \times x) \% p = (x \times m \times b) \% p = (x \times m \times b) \% p\),因此可以得到:\((b \times x) \% p = 1\)。可以看出x是b在模p意义下的逆元。

由以上过程我们看到,求\((a \div b) \% p\) 等同于求$a \times (b $ 的逆元 \() \% p\)。因此求模运算的除法问题就转化为求一个数的逆元问题了。

4.3 如何求乘法逆元

1.费马小定理(模数为素数且\(p \nmid a\))

因为 \(ax \equiv 1 \pmod p\)

所以 \(ax \equiv a^{p-1} \pmod p\)

所以 \(x \equiv a^{p-2} \pmod p\)

inline ll quickpow(ll a, ll n, ll p) {  //求 a ^ n % p
	ll ans = 1;
	while (n) {
		if (n & 1) ans = ans * a % p;
		a = a * a % p;
		n >>= 1;
	}
	return ans;
}
inline ll niyuan(ll a, ll p) {  //求 a ^ (p - 2) % p
	return quickpow(a, p - 2, p);
}

2.欧拉定理求逆元(a与p互质)

若 \(gcd(a, p) = 1\),则\(a^{\varphi (p)} \equiv 1 \pmod p\)。

那么\(a^{\varphi(p) - 1}\)即为a在模p意义下的逆元。

inline ll inv(ll a, ll p) {
	ll phi = p, mod = p;
	for (rg int i = 2; i * i <= p; i++) {
		if (p % i == 0) {
			phi = phi / i * (i - 1);
			while (p % i == 0) p /= i;
		}
	}
	if (p > 1) phi = phi / p * (p - 1);
	phi--;
	ll res = 1, t = a;
	while (phi) {
		if (phi & 1) res = (res * t) % mod;
		t = (t * t) % mod;
		phi >>= 1;
	}
	return res;
}

3.扩展欧几里得求逆元(a与p互质)

我们将\(ax \equiv 1 \pmod p\)转换成\(ax = p(-y) + 1\),移项得 \(ax + py = 1\),可用扩欧求解,逆元为
\((res \% p + p) \% p\)。

inline ll exgcd(ll a, ll b, ll &x, ll &y) {
	if (b == 0) {
		x = 1;
		y = 0;
		return a;
	}
	rg ll res = exgcd(b, a % b, x, y);
	rg ll t = x;
	x = y;
	y = t - a / b * y;
	return res;
}

4.线性求逆元 (模数为质数)

首先,\(1^{-1} \equiv 1 \pmod p\)。设\(p = k \times i + r\),其中\(1 < i < p, r < i\),再放到模p意义下,则有:\(k \times i + r \equiv 0 \pmod p\)。两边同时乘以\(i^{-1} \times r^{-1}\),则:

\[\begin{aligned} k \times i \times i^{-1} \times r^{-1} + r \times i^{-1} \times r^{-1} \equiv 0 \pmod p \\\ k \times r^{-1} + i^{-1} \equiv 0 \pmod p \\\ i^{-1} \equiv -k \times r ^ {-1} \pmod p \\\ i^{-1} \equiv -\lfloor \frac p i \rfloor \times r^{-1} \pmod p \\\ i^{-1} \equiv -\lfloor \frac p i \rfloor \times (p \bmod i) ^ {-1} \pmod p\end{aligned} \]

设\(t = \frac p i, k = p \% i\),有:\(p = i \times t + k\),即$i \times t + k \equiv 0 \pmod p \(,即\)k \equiv -i \times t \pmod p\(。两边同时除以\)i \times k\(,有\)\frac 1 i \equiv -\frac t k \pmod p\(,将k,t带入有\)inv[i] \equiv -\frac p i \times inv[p % i] \pmod p\(,为防止有负数,有\)inv[i] = (p - \frac p i) \times inv[p % i]) % p$。

ll ny[MAXN];
inline void get_inv(int n, int p) {
	ny[1] = 1;
	for (rg int i = 2; i <= n; i++) {
		ny[i] = (ll)(p - p / i) * ny[p % i] % p;
	}
}

4.4 补充

1.逆元是(完全)积性函数,所以在求\(2 ^ m x \equiv 1 \pmod b\)时可以直接求出2在\(\bmod b\)意义下的逆元,这个数的m次方\(\bmod b\)就是\(2^m\)在\(\bmod b\)意义下的逆元。

5 计数原理

1.加法原理:

完成一件事情共有n类方法,第一类方法有\(n_1\)种方案,第二类有\(n_2\)种方案\(\cdots\)那么完成这件事情共有\(n_1 + n_2 + \cdots\)种方法,注意分类需要不重不漏。

2.乘法原理:

完成一件事情需要分成n步进行,第一步有\(n_1\)种方法,第二步有\(n_2\)种方法\(\cdots\)那么完成这件事共有\(n_1 * n_2 * \cdots\)种方法。

3.排列与组合

(1)排列:从n个元素中取出m个元素进行排序,用符号\(A_n^m\)表示。

\(A(n, m)=n * (n-1) * (n-2) * \cdots * (n - m + 1) = \frac {n!} {(n-m)!}\)

(2)组合:从n个元素中仅仅取出m个元素,不考虑排序,用符号\(C_n^m\)表示。

\(C(n, m) = A(n, m) / A(m, m) = \frac {n!} {m!(n-m)!}\)

4.常用策略方法

(1)特殊元素和特殊位置优先策略

(2)相邻元素捆绑策略

(3)不相邻问题插空策略

(4)定序问题倍缩空位插入策略

例:7人排队,其中甲乙丙3人顺序一定,共有多少不同的排法?

共有\(\frac {A(7, 7)}{A(3,3)}\)。

(5)排列问题求幂策略

(6)环排问题线排策略

(7)多排问题直排策略

(8)排列组合混合问题先选后排策略

(9)平均分组问题除法策略

(10)重排列

(11)错位排序

错排问题指一个有n个元素的排列,如若一个排列使得所有的元素不在原来的位置上,则称这个排列为原排列的一个错排。

用\(D(n)\)表示n个元素的排列的错排数。

\(D(n)=(n-1)* (D(n-1)+D(n-2))\)。

特殊地,\(D(1)=0,D(2)=1\)。

6 组合数取模

6.1 求组合数

1.递推法:

有q\((q \le 10000)\)组询问,每组询问两个整数n,m\((1 \le m \le n \le 2000)\),求\(C_{n} ^ {m} \bmod (10 ^ 9 + 7)\)。

递推式:\(C_{n} ^ {m} = C_{n - 1} ^ {m} + C_{n - 1} ^ {m - 1}\)

从n个不同数中选出m个不同方案数是\(C_{n} ^ {m}\),对第1个数有选和不选两种决策:若不选,则从剩下的\(n -1\)中选m个,即\(C_{n - 1} ^ {m}\)。若选,则从剩下的\(n - 1\)中选\(m - 1\)个,即\(C_{n - 1} ^ {m - 1}\)。

const int N = 2010, mod = 1e9 + 7;
int c[N][N];
inline void init() {
	for (rg int i = 0; i < N; i++) {
		c[i][0] = 1;
	}
	for (rg int i = 1; i < N; i++) {
		for (rg int j = 1; j <= i; j++) {
			c[i][j] = (c[i - 1][j] + c[i - 1][j - 1]) % mod;
		}
	}
}

2.计算法:

有q\((q \le 10000)\)组询问,每组询问两个整数n,m\((1 \le m \le n \le 10^5)\),求\(C_{n} ^ {m} \bmod (10 ^ 9 + 7)\)。

用\(C_{n} ^ {m} = \frac {n!} {(n - m)!m!}\)直接计算。

开两个数组分别存模意义下的阶乘和阶乘的逆元。

用\(f[x]\)存\(x!\bmod p\)的值。

用\(g[x]\)存\((x!)^{-1} \bmod p\)的值

根据费马小定理\(a \times a ^ {p - 2} \equiv 1 \pmod p\),可以用快速幂求逆元:

\(C _ n ^ m \pmod p = f[n] \times g[n - m] \times g[m] \pmod p\)

ll f[N], g[N];
inline ll qpow(ll a, int b) {
	rg ll res = 1;
	while (b) {
		if (b & 1) res = res * a % mod;
		a = a * a % mod;
		b >>= 1;
	}
	return res;
}
inline void init() {
	f[0] = g[0] = 1;
	for (rg int i = 1; i < N; i++) {
		f[i] = f[i - 1] * i % mod;
		g[i] = g[i - 1] * qpow(i, mod - 2) % mod;
	}
}
inline ll C(ll n, ll m) {
	return f[n] * g[m] % mod * g[n - m] % mod;
}

另:

计算出每一个逆元的值有些浪费时间,可以在需要时再求,因此有代码:

inline void init() {
	f[0] = 1;
	for (rg int i = 1; i < N; i++) {
		f[i] = f[i - 1] * i % mod;
	}
}
inline ll C(ll n, ll m) {
	return f[n] * qpow(f[m], mod - 2) % mod * qpow(f[n - m], mod - 2) % mod;
}

对lucas也同样适用。

求逆元也可以递推:阶乘的逆元

\(\frac 1 {i!} \pmod p = \frac 1 i \times \frac 1 {(i - 1)!} \pmod p = pow(i, p - 2) \times g[i - 1] \pmod p\)

6.2 大组合数取模(Lucas定理)

1.给定整数n,m,p的值,求\(C _ n ^ m \pmod p\)的值。其中\(1 \le m \le n \le 10^{18}\),\(1 \le p \le 10 ^ 5\),保证p为素数。

2.求解:

对于质数p,有:

\(C _ n ^ m \equiv C _ {\frac n p} ^ {\frac m p} \times C _ {n \bmod p} ^ {m \bmod p} \pmod p\)

上式中,\(n \bmod p\)和\(m \bmod p\)一定小于p,可以直接求解,\(C _ {\frac n p} ^ {\frac m p}\)可以继续用Lucas定理求解。这也就要求p的范围不能够太大,一般在\(10^5\)左右。边界条件:当\(m = 0\)时,返回1。

3.引理

(1)\(C _ p ^ x \equiv 0 \pmod p\)。\((0 < x < p)\)

(2)\((1 + x)^p \equiv 1 + x ^ p \pmod p\)

//模版
#include<bits/stdc++.h>
#define rg register
#define qwq 0
using namespace std;
typedef long long ll;
const int N = 100010;
ll f[N], g[N];
ll p;
inline ll qpow(ll a, ll b, ll p) {
	rg ll res = 1;
	while (b) {
		if (b & 1) res = res * a % p;
		a = a * a % p;
		b >>= 1;
	}
	return res;
}
inline void init() {
	f[0] = g[0] = 1;
	for (rg int i = 1; i <= p; i++) {
		f[i] = f[i - 1] * i % p;
		g[i] = g[i - 1] * qpow(i, p - 2, p) % p;
	}
}
inline ll C(ll n, ll m) {
	if (n < m) return 0;
	return f[n] * g[m] * g[n - m] % p;
}
inline ll lucas(ll n, ll m) {
	if (m == 0) return 1;
	return lucas(n / p, m / p) * C(n % p, m % p) % p;
}
ll q, n, m;
int main() {
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin >> q;
	while (q--) {
		cin >> n >> m >> p;
		init();
		cout << lucas(n + m, n) << "\n";
	}
	return qwq;
}

7 中国剩余定理(CRT)

7.1 简介

「物不知数」问题:有物不知其数,三三数之剩二,五五数之剩三,七七数之剩二。问物几何?

中国剩余定理可求解如下形式的一元线性同余方程组。

\[\begin{cases} x \equiv r_1 \pmod {m_1} \\ x \equiv r_2 \pmod {m_2} \\ \vdots \\ x \equiv a_k \pmod {n_k} \end{cases} \]

求x的最小非负整数解。(其中模数\(m_1, m_2, \cdots m_k\)为两两互质整数)

7.2 过程

1.计算所有模数的乘积\(M = m_1 \times m_2 \times m_3 \times \cdots \times m_n\);

2.对于第i个方程:

a.计算\(c_i = \frac M {m_i}\)

b.计算\(c_i\)在模\(m_i\)意义下的逆元\(c_i ^ {-1}\)。(逆元一定存在,因为\(c_i\)中已经把\(m_i\)除掉了)

3.方程组的唯一解为:\(x = \sum _ {i = 1} ^ n r_i c_i c_i^{-1} \bmod M\)。

例:

\[\begin{cases} x \equiv 2 \pmod 3 \\ x \equiv 3 \pmod 4 \\ x \equiv 1 \pmod 5 \end{cases} \]

1.\(M = 3 \times 4 \times5 = 60\)

2.\(c_1 = 20\),\(c_1 ^ {-1} = 2\)

\(c_2 = 15\),\(c_2^{-1} = 3\)

\(c_3 = 12\),\(c_3^{-1} = 3\)

3.\(x = (2 \times 20 \times 2 + 3 \times 15 \times + 1 \times 12 \times 3) \% 60 = 11\)

#include<bits/stdc++.h>
#define rg register
#define qwq 0
using namespace std;
typedef long long ll;
ll n, a[11], b[11];
inline ll exgcd(ll a, ll b, ll &x, ll &y) {
	if (b == 0) {
		x = 1;
		y = 0;
		return a;
	}
	rg ll ret = exgcd(b, a % b, x, y);
	rg ll t = x;
	x = y;
	y = t - a / b * y;
	return ret;  //x就是逆元
}
inline ll CRT(ll m[], ll r[]) {
	ll M = 1, ans = 0;
	for (rg int i = 1; i <= n; i++) M *= m[i];
	for (rg int i = 1; i <= n; i++) {
		rg ll c = M / m[i], x, y;
		exgcd(c, m[i], x, y);
		ans = (ans + r[i] * c * x % M) % M;
	}
	return (ans % M + M) % M;
}
int main() {
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin >> n;
	for (rg int i = 1; i <= n; i++) {
		cin >> a[i] >> b[i];
	}
	cout << CRT(a, b) << "\n";
	return qwq;
}

7.3 扩展卢卡斯定理

Lucas定理中要求模数p必须为素数,那么对于p不是素数的情况,就要用到exLucas。

1.题目:(P4720 【模板】扩展卢卡斯定理/exLucas)

求 \(C _ n ^ m \bmod P\)。

2.过程:

(1)第一部分:中国剩余定理

考虑利用中国剩余定理合并答案。

根据唯一分解定理,将P质因数分解:

\(P = p_1^{a_1} \times p_2^{a_2} \times \cdots \times p_r^{a_r} = \prod_{i = 1}^{r} p_i^{a_i}\)

对于任意i,j,有 \(p_i^{a_i}\) 与 \(p_j^{a_j}\) 互质,所以可以构造出如下r个同余方程:

\[\begin{cases} a_1 \equiv C_m^n \pmod {p_1^{a_1}} \\ a_2 \equiv C_m^n \pmod {p_2^{a_2}} \\ \vdots \\ a_r \equiv C_m^n \pmod {p_r^{a_r}} \end{cases} \]

(2)第二部分:移除分子分母中的素数

根据同余的定义,\(a_i = C_m^n \bmod p_i^{a_i}\),问题转化成,求\(C_m^n \bmod p^a\)(p为质数)的值。

根据组合数定义,\(C_m^n \bmod p^a = \frac {n!} {m!(n-m)!} \bmod p^a\)。

由于式子是在模\(p^a\)意义下,所以分母要算乘法逆元。

同余方程\(ax \equiv 1 \pmod p\)(即乘法逆元)有解的充要条件为\(\gcd(a,p) = 1\),然而无法保证有解,发现无法直接求\(inv_{m!}\)和\(inv_{(n-m)!}\),所以将原式转化为:
\(\frac{\frac{n!}{p^x}}{\frac{m!}{p^y}\frac{(n-m)!}{p^z}}p^{x-y-z} \bmod p^a\)

x表示n!中包含多少个p因子,y,z同理。

(3)第三部分:Wilson定理的推论

问题转化成,求形如:\(\frac {n!} {q^x} \bmod q^k\)的值。这时可以利用Wilson定理的推论。

解释:

一个示例:\(22!\bmod 3^2\)

\(22! = 3^7 \times (1 \times 2 \times 3 \times 4 \times 5 \times 6 \times 7) \times (1 \times 2 \times 4 \times 5 \times 7 \times 8 \times 10 \times 11 \times 13 \times 14 \times 16 \times 17 \times 20 \times 22)\)

可以看到,式子分为三个整式的乘积:

1> 是\(q^{\lfloor \frac {n}{q} \rfloor}\);

2> 是\(\lfloor \frac n q \rfloor!\),由于阶乘中仍然有可能有q的倍数,考虑递归求解;

3> 是\(n!\)中与q互质的部分的乘积,具有如下性质:

\(1 \times 2 \times 4 \times 5 \times 7 \times 8 \equiv 10 \times 11 \times 13 \times 14 \times 16 \times 17 \pmod {3^2}\),即:

\(\displaystyle \prod_{i,\gcd(i,q)=1}^{q^k}i\equiv\prod_{i,\gcd(i,q)=1}^{q^k}(i+tq^k) \pmod{q^k}\)(t是任意正整数)。

\(\displaystyle \prod_{i,\gcd(i,q)=1}^{q^k}i\) 一共循环了\(\displaystyle \lfloor\frac{n}{q^k}\rfloor\) 次,暴力求出\(\displaystyle \prod_{i,\gcd(i,q)=1}^{q^k}i\),然后用快速幂求\(\displaystyle \lfloor\frac{n}{q^k}\rfloor\) 次幂。
最后要乘上\(\displaystyle \prod_{i,\gcd(i,q)=1}^{n \bmod q^k}i\),即 \(19\times 20\times 22\),显然长度小于 q^k,暴力乘上去。

上述三部分乘积为\(n!\)。最终要求的是\(\frac{n!}{q^x} \bmod {q^k}\)。

所以有:\(n! = q^{\lfloor \frac n q \rfloor} \times (\lfloor \frac n q \rfloor)! \times (\displaystyle \prod _ {i, \gcd(i,q) = 1} ^ {q^k}i)^{\lfloor \frac{n} {q^k} \rfloor} \times (\prod _ {i, \gcd(i,q) = 1} ^ {n \bmod q^k}i)\)

于是:

\(\frac{n!}{q^{\lfloor \frac n q \rfloor}} = (\lfloor \frac n q \rfloor)! \times (\displaystyle \prod _ {i, \gcd(i,q) = 1} ^ {q^k}i)^{\lfloor \frac{n} {q^k} \rfloor} \times (\prod _ {i, \gcd(i,q) = 1} ^ {n \bmod q^k}i)\)

\((\lfloor \frac{n}{q} \rfloor)!\)同样是一个阶乘,所以也可以分为上述三个部分,于是可以递归求解。

等式的右边两项不含素数 q。事实上,如果直接把 n 的阶乘中所有 q 的幂都拿出来,等式右边的阶乘也照做,这个等式可以直接写成:

\(\frac{n!}{q^{x}} = \frac{\left(\left\lfloor\frac{n}{q}\right\rfloor\right)!}{q^{x'}} \cdot {\left(\prod_{i,\gcd(i,q)=1}^{q^k}i\right)}^{\left\lfloor\frac{n}{q^k}\right\rfloor} \cdot \left(\prod_{i,\gcd(i,q)=1}^{n\bmod q^k}i\right)\)

式中的 x 和 x' 都表示把分子中所有的素数 q 都拿出来。改写成这样,每一项就完全不含 q 了。

递归的结果,三个部分中,左边部分随着递归结束而自然消失,中间部分可以利用 Wilson 定理的推论 0,右边部分就是推论 2 中的 \(\prod_{j\geq 0}(N_j!) _ p\)。

#include<bits/stdc++.h>
#define rg register
#define qwq 0
using namespace std;
typedef long long ll;
inline void exgcd(ll a, ll b, ll &x, ll &y) {
	if (b == 0) {
		x = 1;
		y = 0;
		return ;
	}
	exgcd(b, a % b, x, y);
	rg ll t = x;
	x = y;
	y = t - a / b * y;
}
inline ll gcd(ll a, ll b) {
	if (b == 0) return a;
	return gcd(b, a % b);
}
inline ll inv(ll a, ll p) {
	rg ll x, y;
	exgcd(a, p, x, y);
	return (x + p) % p;
}
inline ll lcm(ll a, ll b) {
	return a / gcd(a, b) * b;
}
inline ll mabs(ll x) {
	return (x > 0 ? x : -x);
}
inline ll qmul(ll a, ll b, ll p) {
	rg ll ret = 0;
	a %= p;
	b %= p;
	while (b) {
		if (b & 1ll) ret = (ret + a) % p;
		b >>= 1ll;
		a = (a + a) % p;
	}
	return ret;
}
inline ll qpow(ll a, ll b, ll p) {
	rg ll ret = 1;
	a %= p;
	while (b) {
		if (b & 1ll) ret = ret * a % p;
		a = a * a % p;
		b >>= 1ll;
	}
	return ret;
}
inline ll F(ll n, ll P, ll PK) {
	if (n == 0) return 1;
	rg ll rou = 1;  //循环节
	rg ll rem = 1;  //余项
	for (rg ll i = 1; i <= PK; i++) {
		if (i % P) rou = rou * i % PK;
	}
	rou = qpow(rou, n/ PK, PK);
	for (rg ll i = PK * (n / PK); i <= n; i++) {
		if (i % P) rem = rem * (i % PK) % PK;
	}
	return F(n / P, P, PK) * rou % PK * rem % PK;
}
inline ll G(ll n, ll P) {
	if (n < P) return 0;
	return G(n / P, P) + n / P;
}
inline ll C_PK(ll n, ll m, ll P, ll PK) {
	rg ll fz = F(n, P, PK), fm1 = inv(F(m, P, PK), PK), fm2 = inv(F(n - m, P, PK), PK);
	rg ll mi = qpow(P, G(n, P) - G(m, P) - G(n - m, P), PK);
	return fz * fm1 % PK * fm2 % PK * mi % PK;
}
ll A[1005], B[1005];
inline ll exlucas(ll n, ll m, ll P) {
	rg ll ljc = P, tot = 0;
	for (rg ll i = 2; i * i <= P; i++) {
		if (ljc % i == 0) {
			rg ll PK = 1;
			while (ljc % i == 0) {
				PK *= i;
				ljc /= i;
			}
			A[++tot] = PK;
			B[tot] = C_PK(n, m, i, PK);
		}
	}
	if (ljc != 1) {
		A[++tot] = ljc;
		B[tot] = C_PK(n, m, ljc, ljc);
	}
	rg ll ans = 0;
	for (rg int i = 1; i <= tot; i++) {
		rg ll M = P / A[i], T = inv(M, A[i]);
		ans = (ans + B[i] * M % P * T % P) % P;
	}
	return ans;
}
ll n, m, P;
int main() {
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin >> n >> m >> P;
	cout << exlucas(n, m, P) << "\n";
	return 0;
}

7.4 扩展中国剩余定理

1.问题:求解线性同余方程组

\[\begin{cases} x \equiv r_1 \pmod {m_1} \\ x \equiv r_2 \pmod {m_2} \\ \vdots \\ x \equiv a_k \pmod {n_k} \end{cases} \]

求x的最小非负整数解。(其中模数\(m_1, m_2, \cdots m_n\)为不一定两两互质的整数)

2.思路:

前两个方程:\(x \equiv r_1 \pmod m_1\),\(x \equiv r_2 \pmod m_2\)

转化为不定方程:\(x = m_1 p + r1 = m_2 q + r_2\),则:\(m_1p - m_2q = r_2 - r_1\)。

由裴蜀定理,

当\(\gcd(m_1, m_2) \nmid (r_2 - r_1)\)时,无解

当\(\gcd(m_1, m_2) \mid (r_2 - r_1)\)时,有解

由扩欧算法,

得特解 \(p = p \times \frac{r_2 - r_1} {gcd}\),\(q = q \times \frac{r_2 - r_1} {gcd}\)

其通解 \(P = p + \frac {m_2} {gcd} \times k\),\(Q = q - \frac{m_1} {gcd} \times k\)

所以\(x = m_1P + r_1 = \frac{m_1 m_2} {gcd} \times k + m_1 p + r_1\)

前两个方程等价合并为一个方程\(x \equiv r \pmod m\),其中\(r = m_1p + r_1\),\(m = lcm(m_1, m_1)\)

所以n个同余方程只要合并\(n - 1\)次,即可求解。

#include<bits/stdc++.h>
#define rg register
#define qwq 0
using namespace std;
typedef __int128 ll;
const int N = 100005;
ll n, m[N], r[N];
inline ll exgcd(ll a, ll b, ll &x, ll &y) {
	if (b == 0) {
		x = 1;
		y = 0;
		return a;
	}
	rg ll ret = exgcd(b, a % b, x, y);
	rg ll t = x;
	x = y;
	y = t - a / b * y;
	return ret;
}
inline ll EXCRT(ll m[], ll r[]) {
	rg ll m1, m2, r1, r2, p, q;
	m1 = m[1], r1 = r[1];
	for (rg int i = 2; i <= n; i++) {
		m2 = m[i];
		r2 = r[i];
		rg ll d = exgcd(m1, m2, p, q);  //d = gcd(m1, m2)
		if ((r2 - r1) % d) return -1;
		p = p * (r2 - r1) / d;  //特解 
		p = (p % (m2 / d) + m2 / d) % (m2 / d);  //因为p可能小于0,变成最小正整数 
		r1 = m1 * p + r1;
		m1 = m1 * m2 / d;
	}
	return (r1 % m1 + m1) % m1;
}
int main() {
	scanf("%lld", &n);
	for (rg int i = 1; i <= n; i++) {
		scanf("%lld%lld", m + i, r + i);
	}
	printf("%lld\n", EXCRT(m, r));
	return qwq;
}

8 容斥原理

8.1 集合的并

1.\(A_1, A_2,\cdots ,A_n\)是n个集合,则这n个集合并集的元素个数是:

\(\left| \bigcup_{i=1}^{n}A_i\right| =\sum_{i = 1} ^n |A_i|-\sum_{i<j}|A_i\cap A_j|+\sum_{i<j<k}|A_i\cap A_j\cap A_k|-\cdots +(-1)^{n-1}|A_1\cap A_2 \cap \cdots\cap A_n|\)

解析:

\(\sum _ {i = 1} ^ n |A_i|\):将每个集合中的元素个数相加,该值大于总元素数,需修正;(系数为\((-1)^0\))

\(\sum _ {i < j} |A_i \cap A_j|\):减去两两相交的元素个数,该值小于总元素数,需修正;(系数为\((-1)^1\))

\(\sum _ {i < j < k} |A_i \cap A_j \cap A_k|\):加上三个集合相交的元素个数,该值大于总元素数,需修正;(系数为\((-1)^2\))

\(\vdots\)

\((-1)^{n - 1} |A_1 \cap A_2 \cap \cdots \cap A_n|\):加上\((-1)^{n - 1}\)乘以\(n - 1\)个集合相交的元素个数;(系数为\((-1)^{n - 1}\))

上述 奇数个集合的交集的元素个数前系数是正数,偶数个集合的交集的元素个数前系数是负数。

2.例题:给定一个整数n和m个不同的质数\(p_1, p_2, \cdots , p_m\)。求:\(1 \sim n\)中能被\(p_1, p_2, \cdots , p_m\)中的至少一个数整除的整数有多少个。

(1)交集的大小等于n除以质数的乘积。即\(|S_1| = \frac n {p_1}\),\(|S_1 \cap S_2| = \frac n {p_1 \times p_2}\),\(|S_1 \cap S_2 \cap S_3| = \frac n {p_1 \times p_2 \times p_3}\)。

(2)使用的二进制位来表示每个集合选与不选的状态。
如 若有3个质数,就需要3个二进制位来表示所有状态。

\(001 \to S_1,010\to S_2, 100\to S_3\),\(011\to S_1\cap S_2,101\to S_1\cap S_3,110\to S_2\cap S_3\),\(111\to S_1 \cap S_2 \cap S_3\)

#include<bits/stdc++.h>
#define rg register
#define qwq 0
using namespace std;
typedef long long ll;
const int N = 20;
int n, m, prim[N];
inline int calc() {
	rg int res = 0;
	for (rg int i = 1; i < 1 << m; i++) {
		rg int t = 1, sign = -1;
		for (rg int j = 0; j < m; j++) {
			if (i & 1 << j) {
				if ((ll)t * prim[j] > n) {
					t = 0;
					break;
				}
				t *= prim[j];
				sign = -sign;
			}
		}
		if (t) res += n / t * sign;
	}
	return res;
}
int main() {
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin >> n >> m;
	for (rg int i = 0; i < m; i++) {
		cin >> prim[i];
	}
	cout << calc();
	return qwq;
}

8.2 集合的交

集合的交等于全集减去补集的并

\(| \bigcap_{i=1}^{n} S_i| = |U| - |\bigcup_{i=1}^ {n}\overline{S_i}|\)

右边的补集的并使用容斥原理求解。

9 卡特兰数

9.1 介绍

卡特兰数是组合数学中一个常出现于各种计数问题中的数列。其前几项为(从第零项开始):\(1,1,2,5,14,42,132,429,1430,4862,\cdots\)

卡特兰数\(C[n]\)满足以下递推关系:

\(C[n] = C[0]C[n-1]+C[1]C[n-2]+\cdots+C[n-1]C[0]\)。

9.2 例

以走网格为例,从格点(0,0)走到格点(n,n),只能向右或向上走,并且不能越过对角线的路径的条数,就是卡特兰数,记为\(H_n\)。

1.通项公式:

(1)\(H_n = C_{2n}^n - C_{2n}^{n-1}\)

(2)\(H_n = \frac{1}{n+1} C_{2n}^n\)

(3)\(H_n = \frac{4n-2}{n+1}H_{n-1}\)

2.证明:

(1)证明(1)式:

先求路径总数,在2n次移动中选n次向右移动,即\(C_{2n}^n\)。
再求非法路径,即越过对角线\(y=x\)的路径。如果跨越了\(y=x\)那么一定触碰了\(y=x+1\)。
所有的非法路径与这一条线有至少一个交点,把第一个交点设为(a,a+1),把(a,a+1)之后的路径全部按照\(y=x+1\)这条线对称过去,这样,最后的终点就会变成(n-1,n+1)。

所有的非法路径对称后都唯一对应着一条到(n-1,n+1)的路径,所以非法路径数就是\(C_{2n}^{n-1}\),合法路径数就是\(C_{2n}^n - C_{2n}^{n-1}\)。

(2)证明(2)式:

\(H_n = C_{2n}^{n}-C_{2n}^{n-1} = \frac{(2n)!}{n!n!}-\frac{(2n)!}{(n+1)!(n-1)!}=\frac{(2n)!}{n!(n-1)!}(\frac{1}{n}-\frac{1}{n+1})=\frac{(2n)!}{n!n!(n+1)}=\frac{1}{n+1}C_{2n}{n}\)

9.3 应用

1.n个元素进栈序列为:\(1,2,3, \cdots ,n\),则有多少种出栈序列。

我们将进栈表示为+1,出栈表示为-1,则1 3 2的出栈序列可以表示为:+1,-1,+1,+1,-1,-1。

根据栈本身的特点,每次出栈的时候,必定之前有元素入栈,即对于每个-1前面都有一个+1相对应。因此,出栈序列的所有前缀和必然大于等于0,并且+1的数量等于-1的数量。

观察一下n=3的一种出栈序列:+1,-1,-1,+1,-1,+1。序列前三项和小于0,显然这是个非法的序列。如果将第一个前缀和小于0的前缀,即前三项元素都进行取反,就会得到:-1,+1,+1,+1,-1,+1。此时有3+1个+1以及3-1个-1。

因为这个小于0的前缀和必然是-1,且-1比+1多一个,取反后,-1比+1少一个,则+1变为n+1个,且-1变为n-1个。进一步推广,对于n元素的每种非法出栈序列,都会对应一个含有n+1个+1以及n-1个-1的序列。

假设非法序列为A,对应的序列为B。每个A只有一个“第一个前缀和小于0的前缀”,所以每个A只能产生一个B。而每个B想要还原到A,就需要找到“第一个前缀和大于0的前缀”,显然B也只能产生一个A。

每个B都有n+1个+1以及n-1个-1,因此B的数量为\(C_{2n}^{n+1}\),相当于在长度为2n的序列中找到n+1个位置存放+1。相应的,非法序列的数量也就等于\(C_{2n}^{n+1}\)。

出栈序列的总数量共有\(C_{2n}^{n}\),因此,合法的出栈序列的数量为\(C_{2n}^n-C_{2n}^{n+1}=\frac{1}{n+1}C_{2n}^n\)。

2.n对括号,则有多少种“括号匹配”的括号序列。

n对括号相当于有2n个符号,n个左括号、n个右括号,可以设问题的解为f(2n)。第0个符号肯定为左括号,与之匹配的右括号必须为1第2i+1个字符。

于是得到递推式:\(f(2n)=f(0) \times f(2n-2) + f(2) \times f(2n-4) + \cdots +f(2n-2) \times f(0)\)。

假设\(f(0)=1\),计算一下开始几项,\(f(2)=1,f(4)=2,f(6)=5\)。结合递推式,不难发现f(2n)等于h(n)。

3.n个节点构成的二叉树,共有多少种情形?

首先,根肯定会占用一个结点,那么剩余的n-1个结点可以有如下的分配方式:\((0,n-1),(1,n-2),\cdots ,(n-1,0)\),其中(i,j)表示根的左子树含i个结点,右子树含j个结点。

于是\(f(n)=f(0) \times f(n-1) + f(1) \times f(n-2)+ \cdots + f(n-1) \times f(0)\)。

假设f(0)=1,那么\(f(1)=1,f(2)=2,f(3)=5\)。结合递推式,不难发现f(n)等于h(n)。

4.求一个凸多边形区域划分成三角形区域的方法数。

以凸多边形的一边为基,设这条边的2个顶点为A和B。从剩余顶点中选1个,可以将凸多边形分成三个部分,中间是一个三角形,然后求解左右两个凸多边形。

设n个顶点的凸多边形的解为f(n),于是\(f(n)=f(2) \times f(n-1) + f(3) \times f(n-2) + \cdots + f(n-1) \times f(2)\)。

\(f(2) \times f(n-1)\)表示三个相邻的顶点构成一个三角形,那么另外两个部分的顶点数分别为2和n-1。设f(2)=1,那么\(f(3)=1,f(4)=2,f(5)=5\)。结合递推式,不难发现f(n)等于h(n-2)。

5.在圆上选择2n个点,将这些点成对连接起来使得所得到的n条线段不相交的方法数。

以其中一个点为基点,编号为0,然后按顺时针方向将其他点依次编号。那么与编号为0相连点的编号一定为奇数,否则,这两个编号间含有奇数个点,势必会有点被孤立,即在一条线段的两侧分别有一个孤立点,从而导致两线段相交。设选中的基点为A,与它连接的点为B,那么A和B将所有点分成两部分,一部分位于AB的左边,另一部分位于右边,然后分别求解即可。

于是\(f(n)=f(0) \times f(n-2) + f(2) \times f(n-4) + \cdots + f(n-2) \times f(0)\)。

\(f(0) \times f(n-2)\)表示编号0的点与编号1的点相连,此时位于它们右边的点的个数为0,而位于它们左边的点为2n-2。以此类推,\(f(0)=1,f(2)=1,f(4)=2\)。结合递推式不难发现f(2n)等于h(n)。

10 Prüfer序列

10.1 引入

prufer序列是一种将带标号的树用一个唯一的整数序列表示的方法。

prufer序列可以将一个带标号n个节点的树用[1,n]中的n-2个整数表示。你也可以理解为完全图的生成树与数列之间的双射。常用于组合计数问题中。

10.2 对树建立prufer序列

prufer序列是这样建立的:①每次选择一个编号最小的叶结点并删掉它;②在序列中记录下它连接到的那个节点,即它的父结点;③重复n-2次后只剩下两个结点,算法结束。

例如,这是一个7个结点的树的prufer序列的构造过程:

显然,维护一个小根堆,将叶子结点都丢入。每次从堆顶取出一个叶子结点,将其父结点编号加入序列,然后删去该叶子结点并减小其父结点的度。当父结点度为1时变为叶子结点,丢入小根堆。

vector<vector<int> > adj;
vector<int> pruefer_code() {
	rg int n = adj.size();
	set<int> leafs;
	vector<int> degree(n);
	vector<bool> killed(n, false);
	for (rg int i = 0; i < n; i++) {
		degree[i] = adj[i].size();
		if (degree[i] == 1) leafs.insert(i);
	}
	vector<int> code(n - 2);
	for (rg int i = 0; i < n - 2; i++) {
		rg int leaf = *leafs.begin();
		leafs.erase(leafs.begin());
		killed[leaf] = true;
		rg int v;
		for (rg int j = 0; j < adj[leaf].size(); j++) {
			rg int u = adj[leaf][j];
			if (!killed[u]) v = u;
		}
		code[i] = v;
		if (--degree[v] == 1) leafs.insert(v);
	}
	return code;
}

10.3 prufer序列的线性构造算法

初始时\(p\)的值为编号最小的叶子结点。

①将\(p\)的父结点\(f\)加入序列;②若删去\(p\)结点后\(f\)结点变为叶子结点,且\(f < p\),则此时可以将\(f\)作为新选择的叶子结点进行重复操作直到\(f > p\);③将\(p\)自增直到找到下一个未被删除的叶子结点。

int degree[N], fa[N], prufer[N];
int p, cnt;
inline void make_prufer() {
	for (rg int i = 1; i <= n; i++) {  //寻找编号最小的叶子结点 
		if (degree[i] == 1) {
			p = i;
			break;
		}
	}
	rg int leaf = p;  //leaf为当前选择要删去的叶子结点 
	while (cnt < n - 2) {
		rg int f = fa[leaf];
		prufer[++cnt] = f;
		degree[f]--;
		if (degree[f] == 1 && f < p) {
			leaf = f;
		} else {
			p++;
			while (degree[p] != 1) p++;
			leaf = p;
		}
	}
}

10.4 prufer序列的性质

1.在构造完prufer序列后原树中会剩下两个结点,其中一个一定是编号最大的点n。

2.每个结点在序列中出现的次数是其度数减1(没有出现就是叶子结点)。

10.5 由prufer序列重建树

由性质2可知,每个结点的度数为prufer序列中出现次数+1。

①选择度数为1的最小的节点编号,与当前枚举到的prufer序列的点连接,然后同时减掉两个点的度。
②若度数变成1,则该结点也成为叶子结点。
③到最后剩下两个度数为1的点,其中一个是n。把他们连接。

10.6 线性时间重建树

初始时\(p\)的值为编号最小的未出现在序列中的结点。

①将\(p\)的父结点\(f\)设为prufer序列里尚未使用的第一个元素;②若删去\(p\)结点后\(f\)结点变为叶子结点且\(f < p\),则此时可以将\(f\)作为新选择的叶子结点进行重复操作直到\(f > p\)。③将\(p\)自增直到找到下一个还没删除的叶子结点。

int degree[N], fa[N], prufer[N];
int p, cnt;
inline void make_tree() {
	for (rg int i = 1; i <= n; i++) degree[i] = 1;
	for (rg int i = 1; i <= n - 2; i++) degree[prufer[i]]++;  //度数为出现次数+1 
	for (rg int i = 1; i <= n; i++) {  //找到编号最小的叶子结点 
		if (degree[i] == 1) {
			p = i;
			break;
		}
	}
	rg int leaf = p;  //leaf为当前选择要删去的叶子结点 
	while (cnt < n - 2) {
		rg int f;
		f = fa[leaf] = prufer[++cnt];  //当前结点的父亲为序列未使用的第一位 
		degree[f]--;
		if (degree[f] == 1 && f < p) {
			leaf = f;
		} else {
			p++;
			while (degree[p] != 1) p++;
			leaf = p;
		}
	}
	fa[leaf] = n;  //最后剩下两个结点,一个是n,把它们俩相连即可 
}

10.7 prufer序列的应用

1.无向完全图的不同生成树数:

若该完全图有n个结点,则任意一个长度为n-2的prufer序列都可以重构出其一棵生成树,且各不相同。又因为prufer序列的值域在[1,n],故总数为\(n^{n-2}\)。

这就是Cayley公式。

2.n个点的无根树计数:

同上问题。

3.n个点的有根树计数:

对每棵无根树来说,每个点都可能是根,故总数为\(n^{n-2} \times n = n^{n-1}\)

4.n个点,每点度分别为\(d_i\)的无根树计数:

总排列数为\((n-2)!\),即\(A_{n-2}^{n-2}\)。

其中数字\(i\)出现了\(d_i-1\)次,故其重复的有\((d_i-1)!\)种排列,即\(A_{d_i-1}^{d_i-1}\)。

应当除去重复的,故总个数为\(\frac{(n-2)!}{\prod_{i=1}^{n}(d_i-1)!}\)。

11 BSGS

11.1 用处

求解高次同余方程

给定整数a,b,p,a、p互质,求满足\(a^x \equiv b \pmod p\)的最小非负整数x。

11.2 推导

由扩展欧拉定理:\(a^x \equiv a^{x \bmod \varphi(p)} \pmod p\),
可知\(a^x\)模p意义下的最小循环节为\(\varphi(p)\)。因为\(\varphi(p) < p\),故考虑\(x \in [0,p]\),必能找到最小整数x。

如果暴力枚举,时间是O(p)的。

令\(x=i \times m-j\),其中\(m=\lceil \sqrt p \rceil\),\(i \in [1,m]\),\(j \in [0,m-1]\),

则\(a^{i \times m - j} \equiv b \pmod p\),

即\((a^m)^i \equiv b \times a^j \pmod p\)。

①先枚举j,把\((b \times a^j, j)\)丢入哈希表,如果key重复,用更大的j替代旧的;

②再枚举i,计算\((a^m)^i\),到哈希表中查找是否有相等的key,找到第一个即结束。

则最小的\(x = i \times m - j\)

因为j与i的次数都是\(\sqrt p\)的,所以时间是\(O(\sqrt p)\)

11.3 模版

P3846 [TJOI2007] 可爱的质数/【模板】BSGS

#include<bits/stdc++.h>
#define rg register
#define qwq 0
using namespace std;
typedef long long ll;
inline ll bsgs(ll a, ll b, ll p) {
	a %= p;
	b %= p;
	if (a == 0 && b != 0) return -1;
	if (b == 1) return qwq;
	rg ll m = ceil(sqrt(p)), t = b;
	unordered_map<ll, ll> hash;
	/*
	unordered_map是一个将key和value关联起来的容器,它可以高效的根据单个key值查找对应的value。
	key值应该是唯一的,key和value的数据类型可以不相同。
	unordered_map存储元素时是没有顺序的,只是根据key的哈希值,将元素存在指定位置,所以根据key查找单个value时非常高效,平均可以在常数时间内完成。
	unordered_map查询单个key的时候效率比map高,但是要查询某一范围内的key值时比map效率低。
	可以使用[]操作符来访问key值对应的value值。
	*/
	hash[b] = 0;
	for (rg int j = 1; j < m; j++) {
		t = t * a % p;  //求 b * a^j
		hash[t] = j; 
	}
	rg ll mi = 1;
	for (rg int i = 1; i <= m; i++) {
		mi = mi * a % p;  //求 a^m 
	}
	t = 1;
	for (rg int i = 1; i <= m; i++) {
		t = t * mi % p;
		if (hash.count(t)) return i * m - hash[t];
	}
	return -1;  //无解 
}
int a, p, b;
int main() {
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin >> p >> a >> b;
	rg int res = bsgs(a, b, p);
	if (res == -1) {
		cout << "no solution\n";
	} else {
		cout << res << "\n";
	}
	return qwq;
}

例题1:[CSP-S2019] Emiya 家今天的饭

dp+容斥。

84 pts

我们发现一个方案不合法,有且只会有一个主要食材\(>\)总数的一半,所以我们不妨考虑容斥,用所有方案数量\(-\)不合法数量。

求解所有方案数量

所有方案数量很好求,做一个分组背包即可:
\(f[i][j]\)表示前i种烹饪方式,做了j道菜的方案数。
状态转移:

  • 第i种烹饪方式不做菜:\(f[i][j] += f[i-1][j]\)
  • 第i种烹饪方法做1道主要食材是k的菜:\(f[i][j] += f[i-1][j-1]\times a_{i,k}\)

所有方案数量\(=\displaystyle\sum_{j=1}^{n}f[n][j]\)

优化:我们可以\(O(nm)\)预处理\(s_i=a_{i,1}+a_{i,2}+\cdots+a_{i,m}\)。每个状态即可\(O(1)\)转移。

求解不合法数量

由于性质:所有不合法方案中有且只会有一个主要食材\(>\)总数的一半,我们称那个主要食材为越界食材,我们设越界食材为c。
所以我们不妨先用\(O(m)\)枚举c。那么我们可以把其他食材归结为符合条件的食材,我们便可以用一个维度来记录它选了多少。
设\(dp[i][j][k]\)为前i种烹饪方式,第c种(越界食材)选了j道,其他食材选了k道的方案数。
状态转移:

  • 第i种烹饪方法不做菜:\(dp[i][j][k] += dp[i - 1][j][k]\),\(O(1)\)转移
  • 选第c种(越界食材):\(dp[i][j][k] += dp[i - 1][j - 1][k]\times a_{i,c}(j>0)\),\(O(1)\)转移
  • 选其他食材:\(dp[i][j][k] += \displaystyle\sum_{u=1,u!=c}^{m}dp[i-1][j][k-1]\times a_{i,u}\),\(O(m)\)转移

对答案的贡献:\(\sum dp[n][j][k](j>k)\)

优化:可以滚掉第一维。第三种转移最耗时,考虑用求解所有方案数量优化的思想:\(\displaystyle\sum_{u=1,u!=c}^{m}dp[i-1][j][k-1]\times a_{i,u}=dp[i-1][j][k-1]\times\displaystyle\sum_{u=1,u!=c}^{m}a_{i,u}=dp[i-1][j][k-1]\times(s_i-a_{i,c})\),我们就做到了\(O(1)\)转移。

#include<bits/stdc++.h>
#define rg register
#define qwq 0
#define int long long
using namespace std;
const int N = 103, M = 2003, mod = 998244353;
int n, m;
int a[N][M];
int ans, res, ret;
int f[N][M], dp[N][N][N];
int s[N];
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	cout.tie(nullptr);
	cin >> n >> m;
	for (rg int i = 1; i <= n; i++) {
		for (rg int j = 1; j <= m; j++) {
			cin >> a[i][j];
			s[i] = (s[i] + a[i][j]) % mod;
		}
	}
	//求解合法数量
	ans = 1;
	for (rg int i = 1; i <= n; i++) {
		ans = ans * (s[i] + 1) % mod;
	}
	ans--;
	//求解不合法数量
	for (rg int c = 1; c <= m; c++) {
		memset(dp, 0, sizeof(dp));
		ret = 0;
		dp[0][0][0] = 1;
		for (rg int i = 1; i <= n; i++) {
			for (rg int j = 0; j <= i; j++) {
				for (rg int k = 0; k <= i - j; k++) {
					dp[i][j][k] = (dp[i][j][k] + dp[i - 1][j][k]) % mod;
					if (j > 0) dp[i][j][k] = (dp[i][j][k] + dp[i - 1][j - 1][k] * a[i][c] % mod) % mod;
					if (k > 0) dp[i][j][k] = (dp[i][j][k] + dp[i - 1][j][k - 1] * (s[i] - a[i][c]) % mod) % mod;
				}
			}
		}
		for (rg int i = 1; i <= n; i++) {
			for (rg int j = 0; j <= n - i; j++) {
				if(i > j) res = (res + dp[n][i][j]) % mod;
			}
		}
	}
	cout << ((ans - res) % mod + mod) % mod << "\n";
	return qwq;
}

100 pts

延续84pts的思想,求解不合法数量的\(O(n^3m)\)拖累了我们,我们考虑优化。
我们不关系具体越界食材与其他食材选了多少,只用保证越界食材数\(>\)其他食材数即为不合法状态,不妨把这两个的差作为一个维度,这样既可让dp状态降一维:
\(dp[i][j]\)表示前i种烹饪方法,越界食材数\(-\)其他食材数为j的方案数。

状态转移:

  • 第i种烹饪方法不选:\(dp[i][j] += dp[i-1][j]\)
  • 选越界食材:\(dp[i][j] += dp[i - 1][j-1]\times a_{i,c}\)
  • 选其他食材:\(dp[i][j] += dp[i - 1][j+1]\times(s_i-a_{i,c})\)

答案贡献:\(\sum dp[n][j](j>0)\)
总时间复杂度\(O(n^2m)\)。

注意:做差可能为负数,我们把所有状态加一个n就可以了。

#include<bits/stdc++.h>
#define rg register
#define qwq 0
#define int long long
using namespace std;
const int N = 103, M = 2003, mod = 998244353;
int n, m;
int a[N][M];
int ans, res, ret;
int f[N][M], dp[N][N + N];
int s[N];
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	cout.tie(nullptr);
	cin >> n >> m;
	for (rg int i = 1; i <= n; i++) {
		for (rg int j = 1; j <= m; j++) {
			cin >> a[i][j];
			s[i] = (s[i] + a[i][j]) % mod;
		}
	}
	//求解合法数量
	ans = 1;
	for (rg int i = 1; i <= n; i++) {
		ans = ans * (s[i] + 1) % mod;
	}
	ans--;
	//求解不合法数量
	for (rg int c = 1; c <= m; c++) {
		memset(dp, 0, sizeof(dp));
		//dp[0][0] = 1;
		dp[0][n] = 1;
		for (rg int i = 1; i <= n; i++) {
			for (rg int j = 0; j <= 2 * n; j++) {
				dp[i][j] = (dp[i][j] + dp[i - 1][j]) % mod;
				dp[i][j] = (dp[i][j] + dp[i - 1][j - 1] * a[i][c]) % mod;
				dp[i][j] = (dp[i][j] + dp[i - 1][j + 1] * (s[i] - a[i][c]) % mod) % mod; 
			}
		}
		for (rg int i = n + 1; i <= 2 * n; i++) {
			res = (res + dp[n][i]) % mod;
		}
	}
	cout << ((ans - res) % mod + mod) % mod << "\n";
	return qwq;
}

例题2:[cf869C] The Intriguing Obsession

因为两个同色小岛的距离要大于3,所以:

  • 1.同色岛之间不能相连。
  • 2.一个岛不能同时和两个颜色相同的岛相连。

那么只有三种连法:\(A-B,B-C,A-C\),而且这三种方法都是独立的,所以最终的答案就是这三种方法数相乘。
比如求A色岛与B色岛之间的方法数:
因为一个A色岛只能连一个B色岛,所以它的方法数就是:从a个岛里选0个的方法数\(\times\)从b个岛里选0个的方法数 \(\times\) 0个数的总排列数 \(+\) 从a个岛里选1个的方法数 \(\times\) 从b个岛里选1个的方法数 \(\times\) 1个数的总排列数 \(+\cdots +\) 从a个岛里选a个的方法数 \(\times\) 从b个岛里选a个的方法数 \(\times\) a个数的总排列数。( \(a \le b\) ),
也就是\(\displaystyle\sum_{i=0}^{a}C_a^i\times C_b^i \times i!\)。

#include<bits/stdc++.h>
#define rg register
#define qwq 0
#define int long long
using namespace std;
const int N = 5003, mod = 998244353;
int f[N], g[N];
inline int qpow(int a, int b) {
	rg int res = 1;
	while (b) {
		if (b & 1) res = res * a % mod;
		a = a * a % mod;
		b >>= 1;
	}
	return res;
}
inline void init() {
	f[0] = g[0] = 1;
	for (rg int i = 1; i < N; i++) {
		f[i] = f[i - 1] * i % mod;
		g[i] = g[i - 1] * qpow(i, mod - 2) % mod;
	}
}
inline int C(int n, int m) {
	return f[n] * g[m] % mod * g[n - m] % mod;
}
inline int solve(int a, int b) {
	rg int res = 0;
	for (rg int i = 0; i <= min(a, b); i++) {
		res = (res + C(a, i) * C(b, i) % mod * f[i] % mod) % mod;
	}
	return res % mod;
}
int a, b, c;
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	cout.tie(nullptr);
	init();
	cin >> a >> b >> c;
	cout << solve(a, b) * solve(a, c) % mod * solve(b, c) % mod << "\n";
	return qwq;
}

例题3:Lengthening Sticks

首先计算出所有情况数,然后减去不合法的情况数即可。
对于总情况数:
枚举可发现规律:\(tot=\displaystyle\sum_{i=1}^{l+1}\frac{(i + 1)\cdot i}{2}\)。
对于不合法情况:
依次枚举三条边作为最长边的不合法情况,即最长边\(\ge\)其余两边之和。
注意特判即使加上全部l也比最长边小的情况,直接输出0。

#include<bits/stdc++.h>
#define rg register
#define qwq 0
#define int long long 
using namespace std;
const int N = 3e5 + 3;
int a, b, c, l;
int tot;
inline int solve(int a, int b, int c) {  //最长边为a 
	rg int d = b + c - a, nl = l, res = 0;
	if (d > 0) {
		nl -= d;
		a += d;
	}
	for (rg int i = 0; i <= nl; i++) {  //分给a的长度
		/*for (rg int j = 0; j <= nl - i && b + c + j <= a + i; j++) {
			res += j + 1;
		}*/
		rg int maxx = min(nl - i, a + i - b - c);
		res += (maxx + 2) * (maxx + 1) / 2;
	}
	return res;
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	cout.tie(nullptr);
	cin >> a >> b >> c >> l;
	rg int maxx = max(a, max(b, c));
	if (maxx >= a + b + c - maxx + l) {
		cout << 0 << "\n";
		return qwq;
	}
	for (rg int i = 1; i <= l + 1; i++) {
		tot += (i + 1) * i / 2;
	}
	cout << tot - solve(a, b, c) - solve(b, a, c) - solve(c, a, b) << "\n";
	return qwq;
}

标签:pmod,frac,数论,ll,times,int,rg,整理
From: https://www.cnblogs.com/Baiyj/p/18242885

相关文章

  • 整理好了!2024年最常见 20 道分布式、微服务面试题(十)
    上一篇地址:整理好了!2024年最常见20道分布式、微服务面试题(九)-CSDN博客十九、如何设计一个高可用的分布式系统?设计一个高可用的分布式系统是一个复杂的过程,需要考虑多个方面以确保系统的鲁棒性、可扩展性和容错性。以下是一些关键的设计原则和实践:1. 冗余设计:数据冗余:通......
  • 整理好了!2024年最常见 20 道分布式、微服务面试题(九)
    上一篇地址:整理好了!2024年最常见20道分布式、微服务面试题(八)-CSDN博客十七、什么是断路器模式,它如何解决服务依赖问题?断路器模式(CircuitBreakerPattern)是一种软件设计模式,用于处理分布式系统中的服务依赖问题。当一个服务由于某些原因变得不稳定或不可用时,断路器模式可以......
  • 53道Java基础高频题整理(附答案背诵版)
    Java为什么被称为平台无关性语言?Java被称为平台无关性语言,是因为一旦Java代码被编译成字节码,这些字节码就可以在任何安装了Java虚拟机(JVM)的设备上运行,无论这个设备使用的是什么操作系统。这就是“一次编写,到处运行”的理念。Java的这种平台无关性主要得益于Java虚拟机(JVM)......
  • 85道Spring高频题整理(附答案背诵版)
    请阐述Spring框架的基本概念。?Spring框架是一个开源的企业级应用开发框架,由RodJohnson创建,并于2003年首次发布。Spring是在全方位提供企业级服务的基础上,用Java实现的。Spring的核心思想是使现代Java开发更加简单。Spring框架以其灵活性和透明性闻名,几乎可以用在任何Ja......
  • nofile参数的学习与整理
    nofile参数的学习与整理背景前段时间正好总结了文件描述符泄露的问题.最近在客户现场,也遇到了一个问题.其实两个问题都是因为nofile参数限制所引发.所以总结一下:nginx的worker的连接数是受到到nofile的限制的.虽然那可以通过修改配置文件和直接ulimit-HSn进......
  • 面试专区|【39道Vi Vim高频题整理(附答案背诵版)】
    1.请简单描述VI编辑器的使用?VI编辑器是一种模式化的文本编辑器,广泛用于Unix和类Unix操作系统。它最初由BillJoy在1976年为BSDUnix编写。VI的特点是它分为三种主要模式:命令模式、插入模式和末行模式。命令模式:这是VI打开文件后默认进入的模式。在此模式下,您可以使用键盘......
  • 面试专区|【40道Bash Shell高频题整理(附答案背诵版)】
    1.简述如何调试Shell脚本?调试Shell脚本是一个帮助开发者识别和修正脚本中错误的过程。Bash提供了多种方式来调试脚本,其中包括:使用-x选项:通过在运行脚本时使用-x选项,Bash会在执行每一行命令之前打印该命令。这有助于查看脚本的执行流程和变量的值变化。例如,如......
  • 浙大版PTA python程序设计 第四章题目及知识点解析整理
    第四章--1--在循环中continue语句的作用是(结束本次循环)退出循环的当前迭代  √ 带有else子句的循环如果因为执行了break语句而退出的话,会执行else子句的代码。×因为break是跳出整个循环,所以如果循环体内有else子句,且循环是通过break退出的,那么else子句中的代码也不......
  • java期末细节知识整理(二)
    1.int这种叫基本数据类型,Integer这种叫包装类,把基本数据类型变为包装类类型的过程叫做装箱,把包装类类型变为基本数据类型的过程叫做拆箱,而其中又分为自动装箱/拆箱和显示装箱/拆箱2.next()方法一定要读取到有效字符后才可以结束输入,会自动去掉输入有效字符之前遇到的空格键,Tab键......
  • RPA-UiBot6.0数据整理机器人—杂乱数据秒变报表
      前言    友友们是否常常因为杂乱的数据而烦恼?数据分类、排序、筛选这些繁琐的任务是否占据了友友们的大部分时间?这篇博客将为友友们带来一个新的解决方案,让我们共同学习如何运用RPA数据整理机器人,实现杂乱数据的快速整理,为你的工作减负增效!    在这里,友友......