当前位置: 首页 > news >正文

【数论】中国剩余定理(CRT) 扩展中国剩余定理(EXCRT)

文章目录

  • 预备知识
  • 一、线性同余方程
  • 二、中国剩余定理(CRT)
    • 1. 求解过程
    • 2. 示例
    • 3.【模板】中国剩余定理 ⭐⭐⭐
  • 三、扩展中国剩余定理(EXCRT)
    • 1. 求解过程
    • 2. 示例
    • 3.【模板】扩展中国剩余定理 ⭐⭐⭐⭐
  • 四、练习
    • 1. 猜数字 ⭐⭐⭐
      • (1) 解题思路
      • (2) 代码实现
    • 2. 屠龙勇士 ⭐⭐⭐⭐⭐
      • (1) 解题思路
      • (2) 代码实现

预备知识

  • 乘法逆元

一、线性同余方程

南北朝时期《孙子算经》中有个问题:有物不知其数,三三数之剩二,五五数之剩三,七七数之剩二。问物几何?

用数学公式翻译过来就是一个方程组:
{ x ≡ 2 ( m o d 3 ) x ≡ 3 ( m o d 5 ) x ≡ 2 ( m o d 7 ) \begin{cases} x\equiv 2\pmod 3 \\ \\ x\equiv 3\pmod 5 \\ \\ x\equiv 2\pmod 7 \end{cases} x2(mod3)x3(mod5)x2(mod7)
像这样一些一次同余方程构成的方程组叫做线性同余方程组。最常见的线性同余方程组为:
{ x ≡ r 1 ( m o d m 1 ) x ≡ r 2 ( m o d m 2 ) ⋯ x ≡ r n ( m o d m n ) \begin{cases} x\equiv r_1\pmod {m_1} \\ \\ x\equiv r_2\pmod {m_2} \\ \\ \cdots \\ \\ x\equiv r_n\pmod {m_n} \end{cases} xr1(modm1)xr2(modm2)xrn(modmn)
如果模数 m 1 , m 2 , ⋯ , m n m_1, m_2, \cdots, m_n m1,m2,,mn 两两互质,我们该如何求解这样一个方程组的通解呢?这就需要用到中国剩余定理了。


二、中国剩余定理(CRT)

1. 求解过程

中国剩余定理(CRT)可以求解线性同余方程,前提条件是这些同余方程的模数两两之间必须互质。CRT 求解同余方程组的过程如下:

m 1 , m 2 , ⋯ , m n m_1, m_2, \cdots, m_n m1,m2,,mn 两两互质,对于一个线性同余方程组
{ x ≡ r 1 ( m o d m 1 ) x ≡ r 2 ( m o d m 2 ) ⋯ x ≡ r n ( m o d m n ) \begin{cases} x\equiv r_1\pmod {m_1} \\ \\ x\equiv r_2\pmod {m_2} \\ \\ \cdots \\ \\ x\equiv r_n\pmod {m_n} \end{cases} xr1(modm1)xr2(modm2)xrn(modmn)
M = m 1 × m 2 × ⋯ × m n M = m_1\times m_2\times\cdots\times m_n M=m1×m2××mn,构造:
C 1 = r 1 × M m 1 × ( M m 1 ) − 1 C 2 = r 2 × M m 2 × ( M m 2 ) − 1 ⋯ C n = r n × M m n × ( M m n ) − 1 C_1 = r_1\times \frac{M}{m_1}\times (\frac{M}{m_1})^{-1} \\ C_2 = r_2\times \frac{M}{m_2}\times (\frac{M}{m_2})^{-1} \\ \cdots \\ C_n = r_n\times \frac{M}{m_n}\times (\frac{M}{m_n})^{-1} \\ C1=r1×m1M×(m1M)1C2=r2×m2M×(m2M)1Cn=rn×mnM×(mnM)1
其中 ( M m i ) − 1 (\frac{M}{m_i})^{-1} (miM)1 表示 M m i \frac{M}{m_i} miM 在模 m i m_i mi 意义下的乘法逆元,则通解 x x x 满足
x ≡ ( C 1 + C 2 + ⋯ + C n ) ( m o d M ) x \equiv (C_1 + C_2 + \cdots + C_n)\pmod M x(C1+C2++Cn)(modM)
因此可以发现,该同余方程组的最小正整数解为
( C 1 + C 2 + ⋯ + C n ) m o d M (C_1 + C_2 + \cdots + C_n) \bmod M (C1+C2++Cn)modM


2. 示例

求解线性同余方程
{ x ≡ 2 ( m o d 3 ) x ≡ 3 ( m o d 5 ) x ≡ 2 ( m o d 7 ) \begin{cases} x\equiv 2\pmod 3 \\ \\ x\equiv 3\pmod 5 \\ \\ x\equiv 2\pmod 7 \end{cases} x2(mod3)x3(mod5)x2(mod7)
的最小非负整数解。

由于 3 , 5 , 7 3, 5, 7 3,5,7 三个数两两互质,因此满足中国剩余定理的使用条件。

首先计算出 M = m 1 × m 2 × m 3 = 3 × 5 × 7 = 105 M = m_1\times m_2\times m_3 = 3\times 5\times 7 = 105 M=m1×m2×m3=3×5×7=105,接下来计算

  • M m 1 = 105 3 = 35 \frac{M}{m_1} = \frac{105}{3} = 35 m1M=3105=35;由 35 x ≡ 1 ( m o d 3 ) 35x\equiv 1\pmod 3 35x1(mod3),解得 x = 2 x = 2 x=2,因此 ( M m 1 ) − 1 = 2 (\frac{M}{m_1})^{-1} = 2 (m1M)1=2

  • M m 2 = 105 5 = 21 \frac{M}{m_2} = \frac{105}{5} = 21 m2M=5105=21;由 21 x ≡ 1 ( m o d 5 ) 21x\equiv 1\pmod 5 21x1(mod5),解得 x = 1 x = 1 x=1,因此 ( M m 2 ) − 1 = 1 (\frac{M}{m_2})^{-1} = 1 (m2M)1=1

  • M m 3 = 105 7 = 15 \frac{M}{m_3} = \frac{105}{7} = 15 m3M=7105=15;由 15 x ≡ 1 ( m o d 7 ) 15x\equiv 1\pmod 7 15x1(mod7),解得 x = 1 x = 1 x=1,因此 ( M m 2 ) − 1 = 1 (\frac{M}{m_2})^{-1} = 1 (m2M)1=1

则有
C 1 = 2 × 35 × 2 = 140 C 2 = 3 × 21 × 1 = 63 C 3 = 2 × 15 × 1 = 30 C_1 = 2\times 35\times 2 = 140 \\ C_2 = 3\times 21\times 1 = 63 \\ C_3 = 2\times 15\times 1 = 30 C1=2×35×2=140C2=3×21×1=63C3=2×15×1=30
所以该同余方程组的最小非负整数解为
( 140 + 63 + 30 ) m o d 105 = 23 (140 + 63 + 30) \bmod 105 = 23 (140+63+30)mod105=23


3.【模板】中国剩余定理 ⭐⭐⭐

【题目链接】

P1495 【模板】中国剩余定理(CRT)/ 曹冲养猪 - 洛谷

【题目描述】

自从曹冲搞定了大象以后,曹操就开始捉摸让儿子干些事业,于是派他到中原养猪场养猪,可是曹冲满不高兴,于是在工作中马马虎虎,有一次曹操想知道母猪的数量,于是曹冲想狠狠耍曹操一把。举个例子,假如有 16 16 16 头母猪,如果建了 3 3 3 个猪圈,剩下 1 1 1 头猪就没有地方安家了。如果建造了 5 5 5 个猪圈,但是仍然有 1 1 1 头猪没有地方去,然后如果建造了 7 7 7 个猪圈,还有 2 2 2 头没有地方去。你作为曹总的私人秘书理所当然要将准确的猪数报给曹总,你该怎么办?

【输入格式】

第一行包含一个整数 n n n —— 建立猪圈的次数,接下来 n n n 行,每行两个整数 a i , b i a_i, b_i ai,bi,表示建立了 a i a_i ai 个猪圈,有 b i b_i bi 头猪没有去处。你可以假定 a 1 ∼ a n a_1 \sim a_n a1an 互质。

【输出格式】

输出包含一个正整数,即为曹冲至少养母猪的数目。

【示例一】

输入

3
3 1
5 1
7 2

输出

16

【说明/提示】

1 ≤ n ≤ 10 1 \leq n\le10 1n10 0 ≤ b i < a i ≤ 100000 0 \leq b_i\lt a_i\le100000 0bi<ai100000 1 ≤ ∏ a i ≤ 1 0 18 1 \leq \prod a_i \leq 10^{18} 1ai1018


非常明显的一个用中国剩余定理求解的问题。

  1. 计算所有模数的乘积 M M M
  2. 计算 c i = M m i c_i = \frac{M}{m_i} ci=miM
  3. 计算 c i c_i ci 在模 m i m_i mi 意义下的乘法逆元 c i − 1 c_i^{-1} ci1,可利用扩展欧几里得算法求解;
  4. 最终结果为 x = ∑ i = 1 n r i c i c i − 1 m o d M x = \sum\limits_{i = 1}^{n}r_ic_ic_i^{-1}\bmod M x=i=1nricici1modM

注意第四步在相乘的过程中,为了防止溢出,我们通常会采用龟速乘的方式计算。

#include<iostream>using namespace std;typedef long long LL;const int N = 1e5 + 10;
int m[N], r[N];  // 存储模数和余数// 龟速乘
LL qmul(LL a, LL b, LL p)
{LL ret = 0;while(b){if(b & 1) ret = (ret + a) % p;b >>= 1;a = (a + a) % p;}return ret;
}// 扩展欧几里得
void exgcd(LL a, LL b, LL& x, LL& y)
{if(b == 0){x = 1, y = 0;return;}LL x1, y1;exgcd(b, a % b, x1, y1);x = y1;y = x1 - a / b * y1;
}// 中国剩余定理
LL crt(int n)
{// 累乘求 MLL M = 1;for(int i = 1; i <= n; i++) M *= m[i];LL ret = 0;for(int i = 1; i <= n; i++){LL c = M / m[i];LL x, y;exgcd(c, m[i], x, y);  // 扩展欧几里得求逆元x = (x % m[i] + m[i]) % m[i];  // 把 x 补成最小正整数// x * c * r[i]ret = (ret + qmul(qmul(x, c, M), r[i], M)) % M;}return ret;
}int main()
{int n;cin >> n;for(int i = 1; i <= n; i++) cin >> m[i] >> r[i];cout << crt(n) << endl;return 0;
}

三、扩展中国剩余定理(EXCRT)

1. 求解过程

前面我们了解到中国剩余定理(CRT)的使用条件是线性同余方程组的模数必须两两互质,那么如果模数不互质,CRT 就不适用了,此时想要求解模数不两两互质的线性同余方程组,就可以使用扩展中国剩余定理

扩展中国剩余定理的核心思想:迭代。它的求解过程如下:

对于一个线性同余方程组
{ x ≡ r 1 ( m o d m 1 ) x ≡ r 2 ( m o d m 2 ) ⋯ x ≡ r n ( m o d m n ) \begin{cases} x\equiv r_1\pmod {m_1} \\ \\ x\equiv r_2\pmod {m_2} \\ \\ \cdots \\ \\ x\equiv r_n\pmod {m_n} \end{cases} xr1(modm1)xr2(modm2)xrn(modmn)
我们首先补充一个方程: x ≡ r 0 ( m o d m 0 ) x\equiv r_0\pmod {m_0} xr0(modm0),其中 r 0 = 0 r_0 = 0 r0=0 m 0 = 1 m_0 = 1 m0=1。显然这对于任意整数都成立,所以我们把它添加进这个同余方程组中并不会影响最终结果。

假设最终的通解为 r e t = x × m + r ret = x\times m + r ret=x×m+r,初始 r = 0 r = 0 r=0 m = 1 m = 1 m=1。然后通过迭代后面的方程,使得最终 r e t ret ret 依次满足后续的所有方程。

假设当前取得方程为 x ≡ r i ( m o d m i ) x\equiv r_i\pmod{m_i} xri(modmi),即现在我们要让 r e t ret ret 满足 r e t = y × m i + r i ret = y\times m_i + r_i ret=y×mi+ri

现在 r e t ret ret 既满足 r e t = x × m + r ret = x\times m + r ret=x×m+r,又满足 r e t = y × m i + r i ret = y\times m_i + r_i ret=y×mi+ri,所以有
x × m + r = y × m i + r i x\times m + r = y\times m_i + r_i x×m+r=y×mi+ri
于是有
x × m − y × m i = r i − r x\times m-y\times m_i = r_i - r x×my×mi=rir
这样得形式类似于 a x + b y = c ax + by = c ax+by=c,所以我们可以根据扩展欧几里得算法求出特解 x 0 x_0 x0 y 0 y_0 y0,如果无解,则该线性同余方程组无解。

d = gcd ⁡ ( a , b ) d = \gcd(a, b) d=gcd(a,b),根据 exgcd,通解为
x = x 0 + b d × k x = x_0 + \frac{b}{d}\times k x=x0+db×k
带入 r e t = x × m + r ret = x\times m + r ret=x×m+r 中得
r e t = x 0 × m + b d × k × m + r ret = x_0 \times m + \frac{b}{d}\times k \times m + r ret=x0×m+db×k×m+r
整理得到
r e t = k × b d × m + x 0 × m + r ret = k\times\frac{b}{d}\times m + x_0\times m + r ret=k×db×m+x0×m+r
这样的 r e t ret ret,既满足之前的方程,也满足当前的方程。并且这样的 r e t ret ret 又可以看作 r e t = x × m + r ret = x\times m + r ret=x×m+r,其中
m ← m × b d r ← x 0 × m + r m \leftarrow m \times\frac{b}{d} \\r\leftarrow x_0\times m + r mm×dbrx0×m+r
接下来我们只需要再选取一个新的方程继续迭代出一个新的 r e t ret ret,这样不断迭代,就可以让 r e t ret ret 满足方程组中的所有方程,也就得到了最终解。


2. 示例

求解线性同余方程
{ x ≡ 2 ( m o d 3 ) x ≡ 3 ( m o d 5 ) x ≡ 2 ( m o d 7 ) \begin{cases} x\equiv 2\pmod 3 \\ \\ x\equiv 3\pmod 5 \\ \\ x\equiv 2\pmod 7 \end{cases} x2(mod3)x3(mod5)x2(mod7)
的最小非负整数解。

这里虽然模数两两互质,但是依然可以使用扩展中国剩余定理来求解,我们重点掌握其思想。

首先补充一个方程 x ≡ 0 ( m o d 1 ) x \equiv 0\pmod 1 x0(mod1),最终解 r e t = x × 1 + 0 ret = x\times 1 + 0 ret=x×1+0

迭代第一个方程 x ≡ 2 ( m o d 3 ) x\equiv 2\pmod 3 x2(mod3) 得到: r e t = y × 3 + 2 ret = y \times3 + 2 ret=y×3+2

于是有
r e t = x × 1 + 0 = y × 3 + 2 ret = x\times 1 + 0 = y\times 3 + 2 ret=x×1+0=y×3+2

x − 3 y = 2 x - 3y = 2 x3y=2
可以看作 x + 3 y = 2 x + 3y = 2 x+3y=2,之后利用扩展欧几里得求出
x = 2 + 3 × k y = 0 − k x = 2 + 3 \times k\\ y = 0 - k x=2+3×ky=0k
带入 r e t = x × 1 + 0 ret = x\times 1 + 0 ret=x×1+0 中得
r e t = k × 3 + 2 ⇓ r e t = x × 3 + 2 ret = k\times 3 + 2 \\ \Downarrow \\ ret = x\times 3 + 2 ret=k×3+2ret=x×3+2
此时 r e t ret ret 满足第一个方程。继续迭代第二个方程 x ≡ 3 ( m o d 5 ) x\equiv 3\pmod 5 x3(mod5),有
r e t = y × 5 + 3 ret = y\times 5 + 3 ret=y×5+3
于是
r e t = x × 3 + 2 = y × 5 + 3 ret = x\times 3 + 2 = y\times 5 + 3 ret=x×3+2=y×5+3

3 x + 5 y = 1 3x + 5y = 1 3x+5y=1
利用扩展欧几里得求出
x = 2 + 5 × k y = − 1 − 3 × k x = 2 + 5\times k \\ y = -1 - 3\times k x=2+5×ky=13×k
带入 r e t = x × 3 + 2 ret = x\times 3 + 2 ret=x×3+2 中得
r e t = k × 15 + 8 ⇓ r e t = x × 15 + 8 ret = k\times 15 + 8 \\ \Downarrow \\ ret = x\times 15 + 8 ret=k×15+8ret=x×15+8
此时 r e t ret ret 满足第一、二个方程。继续迭代第三个方程 x ≡ 2 ( m o d 7 ) x\equiv 2\pmod 7 x2(mod7),有
r e t = y × 7 + 2 ret = y\times 7 + 2 ret=y×7+2
于是
r e t = x × 15 + 8 = y × 7 + 2 ret = x\times 15 + 8 = y\times 7 + 2 ret=x×15+8=y×7+2

15 x + 7 y = − 6 15x + 7y = -6 15x+7y=6
利用扩展欧几里得算法求出
x = − 6 + 7 × k y = 12 − 15 × k x = -6 + 7\times k\\ y = 12 - 15\times k x=6+7×ky=1215×k
带入 r e t = x × 15 + 8 ret = x\times 15 + 8 ret=x×15+8 中得
r e t = k × 105 − 82 = k × 105 + 23 ret = k\times 105 - 82 = k\times 105 + 23 ret=k×10582=k×105+23
此时 r e t ret ret 满足所有方程,并且 23 23 23 为最小非负整数解。


3.【模板】扩展中国剩余定理 ⭐⭐⭐⭐

【题目链接】

P4777 【模板】扩展中国剩余定理(EXCRT) - 洛谷

【题目描述】

给定 n n n 组非负整数 a i , b i a_i, b_i ai,bi ,求解关于 x x x 的方程组的最小非负整数解。
{ x ≡ b 1 ( m o d a 1 ) x ≡ b 2 ( m o d a 2 ) … x ≡ b n ( m o d a n ) \begin{cases}x\equiv b_1\pmod{a_1}\\x\equiv b_2\pmod{a_2}\\\dots\\x\equiv b_n\pmod{a_n}\end{cases} xb1(moda1)xb2(moda2)xbn(modan)

【输入格式】

输入第一行包含整数 n n n

接下来 n n n 行,每行两个非负整数 a i , b i a_i, b_i ai,bi

【输出格式】

输出一行,为满足条件的最小非负整数 x x x

【示例一】

输入

3
11 6
25 9
33 17

输出

809

【说明/提示】

对于 100 % 100 \% 100% 的数据, 1 ≤ n ≤ 10 5 1 \le n \le {10}^5 1n105 1 ≤ a i ≤ 10 12 1 \le a_i \le {10}^{12} 1ai1012 0 ≤ b i ≤ 1 0 12 0\leq b_i \leq 10^{12} 0bi1012,保证所有 a i a_i ai 的最小公倍数不超过 10 18 {10}^{18} 1018

请注意程序运行过程中进行乘法运算时结果可能有溢出的风险。

数据保证有解。


#include<iostream>using namespace std;typedef long long LL;const int N = 1e5 + 10;LL m[N], r[N];  // 存模数和余数// 龟速乘,防止乘法溢出
LL qmul(LL a, LL b, LL p)
{LL ret = 0;while(b){if(b & 1) ret = (ret + a) % p;b >>= 1;a = (a + a) % p;}return ret;
}// 扩展欧几里得
LL exgcd(LL a, LL b, LL& x, LL& y)
{if(b == 0){x = 1, y = 0;return a;}LL x1, y1, d;d = exgcd(b, a % b, x1, y1);x = y1;y = x1 - a / b * y1;return d;
}LL excrt(int n)
{// 补充的方程初始模数为 1,余数为 0LL M = 1, ret = 0;for(int i = 1; i <= n; i++){LL a = M, b = m[i], c = r[i] - ret;// 构造出 ax + by = c// c 有可能为负数或者很大的数,需要把它补成模 b 下的最小非负整数c = (c % b + b) % b;  // 防溢出// 解 ax + by = c 中的 xLL x, y, d;d = exgcd(a, b, x, y);if(c % d) return -1;LL k1 = b / d;// ax + by = c 的特解 x0 = x * (c / d)x = qmul(x, c / d, k1);  // 防止直接乘会溢出x = (x % k1 + k1) % k1;// 迭代出下一个方程的系数ret = ret + x * M;M = k1 * M;ret = (ret % M + M) % M; }return ret;
}int main()
{int n; cin >> n;for(int i = 1; i <= n; i++) cin >> m[i] >> r[i];cout << excrt(n) << endl;return 0;
}

四、练习

1. 猜数字 ⭐⭐⭐

【题目链接】

[P3868 TJOI2009] 猜数字 - 洛谷

【题目描述】

现有两组数字,每组 k k k 个。

第一组中的数字分别用 a 1 , a 2 , ⋯ , a k a_1,a_2,\cdots ,a_k a1,a2,,ak 表示,第二组中的数字分别用 b 1 , b 2 , ⋯ , b k b_1,b_2,\cdots ,b_k b1,b2,,bk 表示。

其中第二组中的数字是两两互素的。求最小的 n ∈ N n\in \mathbb{N} nN,满足对于 ∀ i ∈ [ 1 , k ] \forall i\in [1,k] i[1,k],有 b i ∣ ( n − a i ) b_i | (n-a_i) bi(nai)

【输入格式】

第一行一个整数 k k k

第二行 k k k 个整数,表示: a 1 , a 2 , ⋯ , a k a_1,a_2,\cdots ,a_k a1,a2,,ak

第三行 k k k 个整数,表示: b 1 , b 2 , ⋯ , b k b_1,b_2,\cdots ,b_k b1,b2,,bk

【输出格式】

输出一行一个整数,为所求的答案 n n n

【示例一】

输入

3
1 2 3
2 3 5

输出

23

【说明/提示】

对于 100 % 100\% 100% 的数据:

1 ≤ k ≤ 10 1\le k \le 10 1k10 ∣ a i ∣ ≤ 1 0 9 |a_i|\le 10^9 ai109 1 ≤ b i ≤ 6 × 1 0 3 1\le b_i\le 6\times 10^3 1bi6×103 ∏ i = 1 k b i ≤ 1 0 18 \prod_{i=1}^k b_i\le 10^{18} i=1kbi1018

每个测试点时限 1 1 1 秒。

注意:对于 C/C++语言,对 64 64 64 位整型数应声明为 long long

若使用 scanfprintf函数(以及 fscanffprintf等),应采用 %lld标识符。


(1) 解题思路

中国剩余定理模板题变形,需要注意的是余数可能为负数,可以在读入余数之后处理为正数,也可以在龟速乘的时候避免将负数传入参数 b b b 中。


(2) 代码实现

#include<iostream>
using namespace std;typedef long long LL;const int N = 20;
LL m[N], r[N];LL qmul(LL a, LL b, LL p) 
{LL ret = 0;while (b) {if (b & 1) ret = (ret + a) % p;b >>= 1;a = (a + a) % p;}return ret;
}void exgcd(LL a, LL b, LL& x, LL& y) 
{if (b == 0) {x = 1, y = 0;return;}LL x1, y1;exgcd(b, a % b, x1, y1);x = y1;y = x1 - a / b * y1;
}LL crt(int n) 
{LL M = 1;for (int i = 1; i <= n; i++) M *= m[i];LL ret = 0;for (int i = 1; i <= n; i++) {LL c = M / m[i];LL x, y;exgcd(c, m[i], x, y);x = (x % m[i] + m[i]) % m[i];// 龟速乘时避免将负数传给参数 b,否则可能死循环ret = (ret + qmul(r[i], qmul(c, x, M), M)) % M;}return ret;
}int main() 
{LL k;scanf("%lld", &k);for (int i = 1; i <= k; i++) scanf("%lld", r + i);for (int i = 1; i <= k; i++) scanf("%lld", m + i);printf("%lld", crt(k));return 0;
}

2. 屠龙勇士 ⭐⭐⭐⭐⭐

【题目链接】

[P4774 NOI2018] 屠龙勇士 - 洛谷

【题目描述】

小 D 最近在网上发现了一款小游戏。游戏的规则如下:

  • 游戏的目标是按照编号 1 → n 1 \rightarrow n 1n 顺序杀掉 n n n 条巨龙,每条巨龙拥有一个初始的生命值 a i a_i ai 。同时每条巨龙拥有恢复能力,当其使用恢复能力时,它的生命值就会每次增加 p i p_i pi ,直至生命值非负。只有在攻击结束后且当生命值 恰好 0 0 0 时它才会死去。
  • 游戏开始时玩家拥有 m m m 把攻击力已知的剑,每次面对巨龙时,玩家只能选择一把剑,当杀死巨龙后这把剑就会消失,但作为奖励,玩家会获得全新的一把剑。

小 D 觉得这款游戏十分无聊,但最快通关的玩家可以获得 ION2018 的参赛资格,于是小 D 决定写一个笨笨的机器人帮她通关这款游戏,她写的机器人遵循以下规则:

  • 每次面对巨龙时,机器人会选择当前拥有的,攻击力不高于巨龙初始生命值中攻击力最大的一把剑作为武器。如果没有这样的剑,则选择 攻击力最低 的一把剑作为武器。
  • 机器人面对每条巨龙,它都会使用上一步中选择的剑攻击巨龙固定的 x x x 次,使巨龙的生命值减少 x × A T K x \times ATK x×ATK
  • 之后,巨龙会不断使用恢复能力,每次恢复 p i p_i pi 生命值。若在使用恢复能力前或某一次恢复后其生命值为 0 0 0 ,则巨龙死亡,玩家通过本关。

那么显然机器人的攻击次数是决定能否最快通关这款游戏的关键。小 D 现在得知了每条巨龙的所有属性,她想考考你,你知道应该将机器人的攻击次数 x x x 设置为多少,才能用最少的攻击次数通关游戏吗?

当然如果无论设置成多少都无法通关游戏,输出 − 1 -1 1 即可。

【输入格式】

第一行一个整数 T T T,代表数据组数。

接下来 T T T 组数据,每组数据包含 5 5 5 行。

  • 每组数据的第一行包含两个整数, n n n m m m ,代表巨龙的数量和初始剑的数量;
  • 接下来一行包含 n n n 个正整数,第 i i i 个数表示第 i i i 条巨龙的初始生命值 a i a_i ai
  • 接下来一行包含 n n n 个正整数,第 i i i 个数表示第 i i i 条巨龙的恢复能力 p i p_i pi
  • 接下来一行包含 n n n 个正整数,第 i i i 个数表示杀死第 i i i 条巨龙后奖励的剑的攻击力;
  • 接下来一行包含 m m m 个正整数,表示初始拥有的 m m m 把剑的攻击力。

【输出格式】

一共 T T T 行。

i i i 行一个整数,表示对于第 i i i 组数据,能够使得机器人通关游戏的最小攻击次数 x x x ,如果答案不存在,输出 − 1 -1 1

【示例一】

输入

2
3 3
3 5 7
4 6 10
7 3 9
1 9 1000
3 2
3 5 6
4 8 7
1 1 1
1 1

输出

59
-1

【说明】

更多样例

更多样例请在附加文件中下载。

样例 2

见附加文件中的 dragon2.indragon2.ans

样例 1 解释

第一组数据:

  • 开始时拥有的剑的攻击力为 { 1 , 9 , 1000 } \{1,9,1000\} {1,9,1000},第 1 1 1 条龙生命值为 3 3 3,故选择攻击力为 1 1 1 的剑,攻击 59 59 59 次,造成 59 59 59 点伤害,此时龙的生命值为 − 56 -56 56,恢复 14 次后生命值恰好为 0 0 0,死亡。

  • 攻击力为 1 1 1 的剑消失,拾取一把攻击力为 7 7 7 的剑,此时拥有的剑的攻击力为
    { 7 , 9 , 1000 } \{7,9,1000\} {7,9,1000},第 2 条龙生命值为 5 5 5,故选择攻击力为 7 7 7 的剑,攻击 59 59 59 次,造成 413 413 413 点伤害,此时龙的生命值为 − 408 -408 408,恢复 68 68 68 次后生命值恰好为 0 0 0,死亡。

  • 此时拥有的剑的攻击力为 { 3 , 9 , 1000 } \{3,9,1000\} {3,9,1000},第 3 3 3 条龙生命值为 7 7 7,故选择攻击力为 3 3 3 的剑,攻击 59 59 59 次,造成 177 177 177 点伤害,此时龙的生命值为 − 170 -170 170,恢复 17 17 17 次后生命值恰好为 0,死亡。

  • 没有比 59 59 59 次更少的通关方法,故答案为 59 59 59

第二组数据:
不存在既能杀死第一条龙又能杀死第二条龙的方法,故无法通关,输出 − 1 -1 1

【提示】

你所用到的中间结果可能很大,注意保存中间结果的变量类型。


(1) 解题思路

假设巨龙的血量为 r r r,剑的攻击力为 a a a,巨龙的恢复能力为 m m m,恢复次数为 y y y。因为攻击次数为 x x x,所以当
r − a x + m y = 0 r - ax + my = 0 rax+my=0
的时候巨龙死亡。移项得到
a x = m y + r ax = my + r ax=my+r
这不就是一个同余方程变来的嘛!也就是
a x ≡ r ( m o d m ) ax\equiv r\pmod m axr(modm)
那么对于每一条巨龙,我们都可以列出一个同余方程,组合起来就是一个同余方程组:
{ a 1 x ≡ r 1 ( m o d m 1 ) a 2 x ≡ r 2 ( m o d m 2 ) ⋯ a n x ≡ r n ( m o d m n ) \begin{cases} a_1x\equiv r_1\pmod {m_1} \\ \\ a_2x\equiv r_2\pmod {m_2} \\ \\ \cdots \\ \\ a_nx\equiv r_n\pmod {m_n} \end{cases} a1xr1(modm1)a2xr2(modm2)anxrn(modmn)
我们要求的最小攻击次数就是该线性同余方程组的最小正整数解,由于 m i m_i mi 之间不一定互质,所以采用扩展中国剩余定理来求解。

和模板题不一样的是,这里 x x x 的前面多了一个系数 a i a_i ai,不过基本的迭代思想依旧是不变的。

我们首先还是补充一个方程: x ≡ r 0 ( m o d m 0 ) x\equiv r_0\pmod {m_0} xr0(modm0),其中 r 0 = 0 r_0 = 0 r0=0 m 0 = 1 m_0 = 1 m0=1

假设最终的通解为 r e t = x ⋅ m + r ret = x\cdot m + r ret=xm+r,初始 r = 0 r = 0 r=0 m = 1 m = 1 m=1。然后开始迭代后面的方程,使得最终 r e t ret ret 依次满足后续的所有方程。

假设当前取得方程为 a i x ≡ r i ( m o d m i ) a_ix\equiv r_i\pmod{m_i} aixri(modmi),即现在我们要让 r e t ret ret 满足
a i ⋅ r e t = y m i + r i a_i \cdot ret = y m_i + r_i airet=ymi+ri
我们把 r e t = x × m + r ret = x\times m + r ret=x×m+r 两边同时乘以 a i a_i ai,得到
a i ⋅ r e t = a i x m + a i r a_i\cdot ret = a_i x m + a_i r airet=aixm+air
所以有
a i x m + a i r = y m i + r i a_i x m + a_i r = y m_i + r_i aixm+air=ymi+ri
整理得
a i x m − y m i = r i − a i r a_ix m-y m_i = r_i - a_ir aixmymi=riair
形式类似于 a x + b y = c ax + by = c ax+by=c,可以用扩展欧几里得算法求出特解 x 0 x_0 x0 y 0 y_0 y0,如果无解,则该线性同余方程组无解,说明无法通关。

d = gcd ⁡ ( a , b ) d = \gcd(a, b) d=gcd(a,b),根据 exgcd,通解为
x = x 0 + b d k x = x_0 + \frac{b}{d} k x=x0+dbk
带入 r e t = x ⋅ m + r ret = x\cdot m + r ret=xm+r 中得
r e t = b d m k + x 0 m + r ret = \frac{b}{d} mk + x_0 m + r ret=dbmk+x0m+r
这样的 r e t ret ret,既满足之前的方程,也满足当前的方程。并且这样的 r e t ret ret 又可以看作新的 r e t = x × m + r ret = x\times m + r ret=x×m+r,其中
m ← m b d r ← x 0 m + r m \leftarrow m \frac{b}{d} \\r\leftarrow x_0 m + r mmdbrx0m+r
接下来我们只需要再选取一个新的方程继续迭代出一个新的 r e t ret ret,这样不断迭代,就可以让 r e t ret ret 满足方程组中的所有方程,也就得到了最终解。

  • 细节问题
  1. 由于我们打每条龙要使用的剑的要求是 “攻击力不高于巨龙初始生命值中攻击力最大的一把剑作为武器。如果没有这样的剑,则选择 攻击力最低 的一把剑作为武器”,因此需要采用二分取找的,并且杀死每条龙之后要选择另一把剑,所以这里我们采用 multiset 去存储剑的信息(攻击力),选择剑的时候调用 lower_bound 或者 upper_bound 接口即可。注意打完每条龙之后要 erase 掉。

  2. 我们最开始推导的时候列出了一个等式 r − a x + m y = 0 r - ax + my = 0 rax+my=0 y y y 表示恢复次数,它必须大于 0 0 0,但是如果像上面这样直接写代码的话 y y y 有可能小于 0 0 0,计算出的 x x x 是不正确的。因此为了确保回复次数 y > 0 y > 0 y>0,我们还需要额外维护一个变量 limit,记录杀死这些龙所需要的最大攻击次数,只有最终的 x x x 大于等于 limit,才是正确的结果,其中

l i m i t = max ⁡ ( l i m i t , ⌈ r i a i ⌉ ) limit = \max(limit, \ \lceil\frac{r_i}{a_i}\rceil) limit=max(limit, airi⌉)

r r r 为巨龙的血量, a a a 为剑的攻击力。

  1. 涉及乘法运算时,能用龟速乘则用龟速乘,防止溢出

(2) 代码实现

#include<iostream>
#include<set>using namespace std;typedef long long LL;const int N = 1e5 + 10;// 所有攻击次数中最大的那个
LL limit;
// 恢复能力, 生命值, 奖励剑的攻击力
LL mod[N], r[N], atk_rwd[N];
// 存储剑的攻击力
multiset<LL> atk;// 获取攻击力不高于 hp 的攻击力最大的剑, 没有选择攻击力最低的一把
LL get_atk(LL hp)
{auto it = atk.upper_bound(hp);if(it != atk.begin()) --it;return *it;
}// 龟速乘
LL qmul(LL a, LL b, LL p)
{LL ret = 0;while(b){if(b & 1) ret = (ret + a) % p;b >>= 1;a = (a + a) % p;}return ret;
}// 扩展欧几里得
LL exgcd(LL a, LL b, LL& x, LL& y)
{if(b == 0){x = 1, y = 0;return a;}LL x1, y1, d;d = exgcd(b, a % b, x1, y1);x = y1;y = x1 - a / b * y1;return d;
}// 扩展中国剩余定理
LL excrt(int n)
{LL M = 1, ret = 0;for(int i = 1; i <= n; i++){// 获取剑的攻击力LL t = get_atk(r[i]);LL a = qmul(t, M, mod[i]), b = mod[i], c = r[i] - ret * t;c = (c % b + b) % b;LL x, y, d;d = exgcd(a, b, x, y);if(c % d) return -1;LL k1 = b / d;x = qmul(x, c / d, k1);x = (x % k1 + k1) % k1;ret = ret + M * x;M = k1 * M;ret = (ret % M + M) % M;// 使用了一把剑之后这把剑就没了, 并奖励一把新的剑atk.erase(atk.find(t));atk.insert(atk_rwd[i]);// 维护 limitlimit = max(limit, (r[i] + t - 1) / t);}// 根据 limit 和 ret 的差值把 ret 补成大于 limit 的最小合法解if(ret < limit) ret = ret + (limit - ret + M - 1) / M;return ret;
}int main()
{int t; cin >> t;while(t--){// 多组测试用例记得清空上一组的数据atk.clear();limit = 0;int n, m;cin >> n >> m;for(int i = 1; i <= n; i++) cin >> r[i];for(int i = 1; i <= n; i++) cin >> mod[i];for(int i = 1; i <= n; i++) cin >> atk_rwd[i];for(int i = 1; i <= m; i++){LL t; cin >> t;atk.insert(t);}cout << excrt(n) << endl;}return 0;
}
http://www.dtcms.com/a/589226.html

相关文章:

  • 【JVM】JVM内存结构
  • 【Vue 功能总结】Vue 注册功能实现:从校验到 API 封装
  • 站长工具收录查询网站建设培训 店
  • 建设家具网站的目的及功能定位做网站月入7000
  • 链表算法---基本算法操作(go语言版)
  • 【开题答辩全过程】以 基于SpringBoot房源出租信息系统的设计与实现为例,包含答辩的问题和答案
  • 解锁MIME:Qt中的数据传输密码
  • 【Go 与云原生】让一个 Go 项目脱离原生的操作系统——我们开始使用 Docker 制造云容器进行时
  • 大语言模型学习之路(一)
  • 网页设计教程网页设计培训福州关键词优化平台
  • GPIO中断编程
  • 手机和pc合一的网站云南集优科技网站
  • 14.大语言模型微调语料构建
  • Docker 40个自动化管理脚本
  • 国外html5网站模版网站建设代码流程
  • 基于多智能体技术的码头车辆最快行驶路径方案重构
  • 网站备案空壳制作微信网站
  • Java119 反射使用
  • 基于springboot付费问答系统【带源码和文档】
  • 焦作官网网站推广工具如何制作一个报名微信小程序
  • Babylon.js中PBRMetallicRoughnessMaterial材质系统深度解析:从基础到工程实践
  • Linuxgit入门设计模式(常考点)
  • Arbess CICD实践(3) - 使用Arbess+GitPuk+sourcefare实现Node.js项目自动化部署+代码扫描
  • 设计网站的流程网站如何做地推
  • 力扣154. 寻找旋转排序数组中的最小值 II
  • React构建工具升级
  • @Transactional注解的切点匹配
  • 建设网站 请示 报告淘宝网站制作公司哪家好
  • leetcode1770.执行乘法运算的最大分数
  • 本溪市城乡住房建设厅网站国外做网站侵权