数论——同余问题全家桶3 __int128和同余方程组

数论——同余问题全家桶3 __int128和同余方程组

  • 快速读写和__int128
    • 快速读写
    • `__int128`
  • 中国剩余定理和线性同余方程组
    • 中国剩余定理(CRT)
    • 中国剩余定理OJ示例
      • 模板题曹冲养猪 - 洛谷
      • 模板题猜数字 - 洛谷
    • 扩展中国剩余定理
    • 扩展中国剩余定理OJ示例
      • 模板题扩展中国剩余定理(EXCRT) - 洛谷
      • NOI2018 屠龙勇士

快速读写和__int128

这块算是补充知识点,但仅作为高精度算法的临时替代,在有的题若使用__int128也会溢出,则只能用高精度算法。

虽然在 99% 的题目中,scanf 以及 printf 来处理输入和输出已经足够快了。但是,如果输入和输出的规模特别庞大,或者出题人卡常,scanfprintf 也是会超时的,这个时候就需要更加快速的读写方式。

同样的,虽然在 99% 的题目中,long long 已经足够我们应付较大的整数。但是,如果题目中数的范围过大,相乘也是有可能超过 long long 的最大范围。如果只是大一点点,此时可以用 __int128 来存储。

快速读写

快速读写有很多种版本,接下来要介绍一种容易实现且够用的版本 - getchar/putchar 结合秦九韶算法。

前置知识:

  1. 在计算机的视角,所有的整数其实是一个一个的字符串,每个数拆开看就是一个一个字符。因此,对于一个整数,可以当成字符串,一个一个字符的输入进来。同理,输出一个整数的时候,也可以当成字符串,一个一个字符的输出。

  2. getchar/putchar 这种输入输出方式相较于 scanf/printf 而言,速度更快。

于是,就可以利用 getchar 将字符转换成整数输入,利用 putchar 将整数转换成字符输出。

如果这种方式还是超时,那就把 getchar 换成 getchar_unlocked。如果换完之后还是超时,那就是毒瘤题,可以忽视,或者就是算法本身的时间复杂度有问题。

但是,getchar_unlocked 只能在 Linux 系统下使用,因此要慎用。

这里通过模板题进行检测:

P10815 【模板】快速读入 - 洛谷

#include<bits/stdc++.h>
using namespace std;inline int read(){int flag=1,ans=0;//这个函数只有在linux操作系统下的gcc系列编译器才能使用 //当然,洛谷也能使用,不然最后一个样例无法通过 char ch=getchar_unlocked();//一般编译器用这个 
//	char ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-')flag=-1;ch=getchar_unlocked();
//		ch=getchar();}while(ch>='0'&&ch<='9'){ans=ans*10+ch-'0';ch=getchar_unlocked();
//		ch=getchar();}return ans*flag; 
}inline void print(int a){if(a<0){putchar('-');a=-a;}if(a>9)print(a/10);putchar(a%10+'0');
}int main() {int n=read();int sum=0,x;while(n--){x=read();sum+=x;}print(sum);return 0;
}

__int128

【更大的整数__int128】

__int128 就是占用 128 字节的整数存储类型,范围就是 − 2 127 ∼ 2 127 − 1 -2^{127} \sim 2^{127} - 1 212721271

− 170141183460469231731687303715884105728 ∼ 170141183460469231731687303715884105727 -170141183460469231731687303715884105728\\\sim170141183460469231731687303715884105727 170141183460469231731687303715884105728170141183460469231731687303715884105727

170 1411 8346 0469 2317 3168 7303 7158 8410 5727//39位
  • 对比int的范围 − 2 32 ∼ 2 32 − 1 -2^{32} \sim 2^{32} - 1 2322321,即 − 2147483648 ∼ 2147483647 -2147483648\sim2147483647 21474836482147483647

    无符号情况下 0 ∼ 4294967295 0\sim 4294967295 04294967295

  • long long − 2 64 ∼ 2 64 − 1 -2^{64} \sim 2^{64} - 1 2642641,即 − 9223372036854775808 ∼ 9223372036854775807 -9223372036854775808\sim9223372036854775807 92233720368547758089223372036854775807

    无符号情况下 0 ∼ 18446744073709551615 0\sim18446744073709551615 018446744073709551615

  • 如果使用了 unsigned __int128,则范围变成 0 ∼ 2 128 0 \sim 2^{128} 02128,即约 39 位数。
    0 ∼ 340282366920938463463374607431768211455 0\sim 340282366920938463463374607431768211455 0340282366920938463463374607431768211455

当数据范围超过 long long,但是还不足以用上高精度算法的时候,用 __int128 是个不错的选择。

但是,__int128 是不能直接用 cincoutscanf 以及 printf 输入或者输出的。只能按照字符的方式输入输出,也就是我们刚刚学习的快速读写的方式。不过,当读入进来之后,用法和普通的整型变量一致。

__int128的使用示例(需要在gcc系IDE使用,例如Devc++):

#include<bits/stdc++.h>
using namespace std;
typedef unsigned __int128 uint;
typedef __int128 _int;inline void print(uint n){if(n>9)print(n/10);putchar(n%10+'0');
}inline void print(_int n){if(n<0){putchar('-');n=-n;}if(n>9)print(n/10);putchar(n%10+'0');
}inline _int read(){_int flag=1,ans=0;char ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-')flag=-1;ch=getchar();}while(ch>='0'&&ch<='9'){ans=ans*10+ch-'0';ch=getchar();}return ans*flag; 
}void f1(){_int mmax=(_int(1)<<127)-1;print(mmax);cout<<endl;_int mmin=(_int(1)<<127);print(mmin+1);//无法直接输出-2^128 cout<<endl;
}void f2(){uint mmax=~uint(0);print(mmax);cout<<endl;
}void f3(){_int a=114514;print(a);cout<<endl;a=read();print(a);cout<<endl;a+=3;print(a);cout<<endl;a-=486;print(a);cout<<endl;a*=1435;print(a);cout<<endl;a/=1435;print(a);cout<<endl;a%=10007;print(a);cout<<endl;
}int main() {
//	f1();
//	f2();f3();return 0;
}

__int128 再好用,缺陷也很多:

  1. 不通用。 __int128 并没有在任何一个 c++ 标准中严格定义,所以目前它只是 GCC 系列编译器的专属。目前测试,只在 Linux 系统下能够正常使用,在其他编译器例如MSVC不能使用。因此如果要使用,就要看比赛中的编译器,是否是 Linux。

  2. 不方便。 __int128 目前是不支持直接读入、输出的。也就是无法用 cincoutscanf 以及 printf 输入或者输出这种类型的数。只能按照字符的方式输入输出,也就是我们刚刚学习的快速读写的方式。

  3. 空间大,速度慢。 __int128 占用了 16 个字节来存,空间超限的风险显著增加。

但是,在有些题目中,__int128 还是足够我们使用的。

中国剩余定理和线性同余方程组

引入线性同余方程组:南北朝时期《孙子算经》中有个问题:有物不知其数,三三数之剩二,五五数之剩三,七七数之剩二。问物几何?

用数学公式翻译过来就是一个线性同余方程组:

{ x ≡ 2 ( mod  3 ) x ≡ 3 ( mod  5 ) x ≡ 2 ( mod  7 ) \begin{cases} x \equiv 2(\text{mod } 3) \\ x \equiv 3(\text{mod } 5) \\ x \equiv 2(\text{mod } 7) \end{cases} x2(mod 3)x3(mod 5)x2(mod 7)

线性同余方程组:求这个方程组的最小非负整数解。

{ x ≡ r 1 ( mod  m 1 ) x ≡ r 2 ( mod  m 2 ) ⋮ x ≡ r n ( mod  m n ) \begin{cases} x \equiv r_1(\text{mod } m_1) \\ x \equiv r_2(\text{mod } m_2) \\ \vdots \\ x \equiv r_n(\text{mod } m_n) \end{cases} xr1(mod m1)xr2(mod m2)xrn(mod mn)

中国剩余定理(CRT)

前提:所有的模数 m 1 , m 2 , . . . , m n m_1, m_2, ..., m_n m1,m2,...,mn 两两互质。因此中国剩余定理的使用比较局限。

原理:中国剩余定理是基于“构造法”得出的结果。以下给出 n = 3 n = 3 n=3 的构造过程, n n n 等于任意数的构造过程是一样的。

对于这个方程组:
{ x ≡ r 1 ( mod  m 1 ) x ≡ r 2 ( mod  m 2 ) x ≡ r 3 ( mod  m 3 ) \begin{cases} x \equiv r_1 (\text{mod } m_1) \\ x \equiv r_2 (\text{mod } m_2)\\x \equiv r_3 (\text{mod } m_3) \end{cases} xr1(mod m1)xr2(mod m2)xr3(mod m3)

{ x m o d m 1 = r 1 x m o d m 2 = r 2 x m o d m 3 = r 3 \begin{cases}x\bmod m_1=r_1\\x\bmod m_2=r_2\\x\bmod m_3=r_3\end{cases} xmodm1=r1xmodm2=r2xmodm3=r3

M = m 1 × m 2 × m 3 M = m_1 \times m_2 \times m_3 M=m1×m2×m3,构造解: x = C 1 + C 2 + C 3 x = C_1 + C_2 + C_3 x=C1+C2+C3,其中:

  • C 1 mod  m 1 = r 1 , C 1 mod  m 2 = 0 , C 1 mod  m 3 = 0 C_1 \text{ mod } m_1 = r_1,\ C_1 \text{ mod } m_2 = 0,\ C_1 \text{ mod } m_3 = 0 C1 mod m1=r1, C1 mod m2=0, C1 mod m3=0

  • C 2 mod  m 1 = 0 , C 2 mod  m 2 = r 2 , C 2 mod  m 3 = 0 C_2 \text{ mod } m_1 = 0, \ C_2 \text{ mod } m_2 = r_2, \ C_2 \text{ mod } m_3 = 0 C2 mod m1=0, C2 mod m2=r2, C2 mod m3=0

  • C 3 mod  m 1 = 0 , C 3 mod  m 2 = 0 , C 3 mod  m 3 = r 3 C_3 \text{ mod } m_1 = 0, \ C_3 \text{ mod } m_2 = 0, \ C_3 \text{ mod } m_3 = r_3 C3 mod m1=0, C3 mod m2=0, C3 mod m3=r3

这样 x m o d m 1 = C 1 m o d m 1 + C 2 m o d m 1 + C 3 m o d m 1 = r 1 x\bmod m1 = C_1\bmod m1+C_2\bmod m1+C_3\bmod m_1=r_1 xmodm1=C1modm1+C2modm1+C3modm1=r1,其他 m 2 m2 m2 m 3 m3 m3同理。

因此只要能构造出这样的 x x x,那么 x x x 就是我们要的解。

C 1 C_1 C1 C 2 C_2 C2 C 3 C_3 C3的构造方式:

  • C 1 = r 1 × m 2 × m 3 × ( m 2 × m 3 ) − 1 , C_1 = r_1 \times m_2 \times m_3 \times (m_2 \times m_3)^{-1}, C1=r1×m2×m3×(m2×m3)1,

    $(m_2 \times m_3)^{-1} 为模 为模 为模m_1 意义下的逆元, 意义下的逆元, 意义下的逆元,m_2 \times m_3 \times (m_2 \times m_3)^{-1}\bmod m_1 最后的结果是 1 。所以 最后的结果是1。所以 最后的结果是1。所以C_1\bmod m_1=r_1 , , C_1\bmod m_2=C_1\bmod m_3=0$。

  • C 2 = r 2 × m 1 × m 3 × ( m 1 × m 3 ) − 1 , C_2 = r_2 \times m_1 \times m_3 \times (m_1 \times m_3)^{-1}, C2=r2×m1×m3×(m1×m3)1, 其中 $(m_1 \times m_3)^{-1} 为模 为模 为模m_1$意义下的逆元。

  • C 3 = r 3 × m 1 × m 2 × ( m 1 × m 2 ) − 1 , C_3 = r_3 \times m_1 \times m_2 \times (m_1 \times m_2)^{-1}, C3=r3×m1×m2×(m1×m2)1, 其中 $(m_1 \times m_2)^{-1} 为模 为模 为模m_1$意义下的逆元。

x x x加减 M M M的若干倍时,依旧是满足方程,因此最小非负整数解就是
( x mod  M + M ) mod  M (x \text{ mod } M + M) \text{ mod } M (x mod M+M) mod M

整个方程的通解是 x + k M x+kM x+kM

以《孙子算经》中的问题为例:
{ x ≡ 2 ( mod  3 ) x ≡ 3 ( mod  5 ) x ≡ 2 ( mod  7 ) \begin{cases} x \equiv 2(\text{mod } 3) \\ x \equiv 3(\text{mod } 5) \\ x \equiv 2(\text{mod } 7) \end{cases} x2(mod 3)x3(mod 5)x2(mod 7)

  1. 计算每一个方程的 C i C_i Ci
    C 1 = r 1 × m 2 × m 3 × ( m 2 × m 3 ) − 1 = 2 × 5 × 7 × 35 − 1 ( mod  3 ) = 2 × 5 × 7 × 2 = 140 C_1 = r_1 \times m_2 \times m_3 \times (m_2 \times m_3)^{-1} = 2 \times 5 \times 7 \times 35^{-1}(\text{mod } 3) = 2 \times 5 \times 7 \times 2 = 140 C1=r1×m2×m3×(m2×m3)1=2×5×7×351(mod 3)=2×5×7×2=140
    C 2 = r 2 × m 1 × m 3 × ( m 1 × m 3 ) − 1 = 3 × 3 × 7 × 21 − 1 ( mod  5 ) = 3 × 3 × 7 × 1 = 63 C_2 = r_2 \times m_1 \times m_3 \times (m_1 \times m_3)^{-1} = 3 \times 3 \times 7 \times 21^{-1}(\text{mod } 5) = 3 \times 3 \times 7 \times 1 = 63 C2=r2×m1×m3×(m1×m3)1=3×3×7×211(mod 5)=3×3×7×1=63
    C 3 = r 3 × m 1 × m 2 × ( m 1 × m 2 ) − 1 = 2 × 3 × 5 × 15 − 1 ( mod  7 ) = 2 × 3 × 5 × 1 = 30 C_3 = r_3 \times m_1 \times m_2 \times (m_1 \times m_2)^{-1} = 2 \times 3 \times 5 \times 15^{-1}(\text{mod } 7) = 2 \times 3 \times 5 \times 1 = 30 C3=r3×m1×m2×(m1×m2)1=2×3×5×151(mod 7)=2×3×5×1=30

  2. 计算结果: x = ( 140 + 63 + 30 ) mod  105 = 233 mod  105 = 23 x = (140 + 63 + 30) \text{ mod } 105 = 233 \text{ mod } 105 = 23 x=(140+63+30) mod 105=233 mod 105=23

推广 n n n取任意数的时候,构造方式也是同理。

  • C 1 = r 1 × m 2 × m 3 × … × m n × ( m 2 × m 3 × … × m n ) − 1 C_1 = r_1 \times m_2 \times m_3 \times \ldots \times m_n \times (m_2 \times m_3 \times \ldots \times m_n)^{-1} C1=r1×m2×m3××mn×(m2×m3××mn)1,
  • C 2 = r 2 × m 1 × m 3 × … × m n × ( m 1 × m 3 × … × m n ) − 1 C_2 = r_2 \times m_1 \times m_3 \times \ldots \times m_n \times (m_1 \times m_3 \times \ldots \times m_n)^{-1} C2=r2×m1×m3××mn×(m1×m3××mn)1,
  • … \ldots
  • C n = r n × m 1 × m 2 × … × m n − 1 × ( m 1 × m 2 × … × m n − 1 ) − 1 C_n = r_n \times m_1 \times m_2 \times \ldots \times m_{n-1} \times (m_1 \times m_2 \times \ldots \times m_{n-1})^{-1} Cn=rn×m1×m2××mn1×(m1×m2××mn1)1

m 1 , m 2 , … , m n m_1, m_2, \ldots, m_n m1,m2,,mn 两两互质时,一定存在这样的 C 1 , C 2 , … , C n C_1, C_2, \ldots, C_n C1,C2,,Cn,因为对应的逆元是一定存在的。且通解为: X = k × lcm + x X = k \times \text{lcm} + x X=k×lcm+x,其中 lcm \text{lcm} lcm因为 m i m_i mi全是质数,所以实际相当于整体的乘积。

总结:在算法竞赛中,中国剩余定理应用的算法流程:

  1. 计算所有模数的乘积 M = m 1 × m 2 × … × m n M = m_1 \times m_2 \times \ldots \times m_n M=m1×m2××mn

  2. 计算第 i i i 个方程的 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 ( mod  M ) x = \sum_{i=1}^n r_i c_i c_i^{-1} (\text{mod } M) x=i=1nricici1(mod M)

中国剩余定理OJ示例

模板题曹冲养猪 - 洛谷

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

1634:【例 4】曹冲养猪

CRT的模板题,因为数据量大,需要使用快速幂改装的龟速乘算法来进行最后的求乘。

龟速乘是能一步出结果的乘法,拆分成若干步乘完,这样做能最大程度保证不溢出,但牺牲了速度。

#include<bits/stdc++.h>
using namespace std;typedef long long LL;//扩展欧几里得算法
void exgcd(LL a, LL b, LL& x, LL& y) {if (!b) {x = 1; y = 0;return;}LL x1, y1;exgcd(b, a % b, x1, y1);x = y1; y = x1 - a / b * y1;
}//龟速乘,防溢出
LL qmul(LL a, LL b, LL m) {LL ans = 0;while (b) {if (b % 2)ans = (ans%m + a%m) % m;a = (a + a) % m;b /= 2;}return ans;
}void ac() {LL n;cin >> n;vector<LL>r(n + 1, 0), m(n + 1, 1);for (LL i = 1; i <= n; i++)cin >> m[i] >> r[i];//中国剩余定理LL M = 1;for (auto& x : m)M *= x;LL ans = 0;for (LL i = 1; i <= n; i++) {LL ci = M / m[i];LL x, y;exgcd(ci, m[i], x, y);x = (x % m[i] + m[i]) % m[i];//快速幂改装的鬼速乘算法ans = (ans % M + qmul(qmul(r[i], ci, M), x, M)) % M;}cout << ans << endl;
}int main() {ios::sync_with_stdio(false);cin.tie(0);int T = 1;//cin >> T;while (T--)ac();return 0;
}

模板题猜数字 - 洛谷

[P3868 TJOI2009] 猜数字 - 洛谷

中国剩余定理的推广模板。题目要求的是 b i ∣ ( n − a i ) b_i\mid (n-a_i) bi(nai),即 ( n − a i ) m o d b i = 0 (n-a_i)\bmod b_i=0 (nai)modbi=0,将等式拆分开就是 n m o d b i − a i m o d b i = 0 n\bmod b_i-a_i\bmod b_i=0 nmodbiaimodbi=0,即 n ≡ a i ( m o d b i ) n\equiv a_i\pmod {b_i} nai(modbi)

因此题目要求的就是同余方程组 n ≡ a i ( m o d b i ) n\equiv a_i\pmod {b_i} nai(modbi)的解, n n n是未知数。

#include<bits/stdc++.h>
using namespace std;typedef long long LL;void exgcd(LL a, LL b, LL& x, LL& y) {if (!b) {x = 1; y = 0;return;}LL x1, y1;exgcd(b, a % b, x1, y1);x = y1; y = x1 - a / b * y1;
}LL qmul(LL a, LL b, LL m) {LL ans = 0;while (b) {if (b % 2)ans = (ans + a) % m;a = (a * 2) % m;b /= 2;}return ans;
}void ac() {int n;cin >> n;vector<LL>a(n + 1, 1), b(a);for (int i = 1; i <= n; i++)cin >> a[i];LL M = 1;for (int i = 1; i <= n; i++)cin >> b[i], M *= b[i];//CRT推广LL ans = 0;for (int i = 1; i <= n; i++) {LL ci = M / b[i];LL x, y;exgcd(ci, b[i], x, y);x = (x % b[i] + b[i]) % b[i];//a[i]为负数,需要放第1个形参,否则会死循环ans = (ans + qmul(qmul(a[i], ci, M), x, M) + M) % M;}cout << ans << endl;
}int main() {ios::sync_with_stdio(false);cin.tie(0);int T = 1;//cin >> T;while (T--)ac();return 0;
}

扩展中国剩余定理

中国剩余定理只能处理所有模数两两互质的情况,如果模数不互质,CRT就不适用了。

【扩展中国剩余定理 - EXCRT】

同样是求线性同余方程组:

{ x ≡ r 1 ( mod  m 1 ) x ≡ r 2 ( mod  m 2 ) ⋮ x ≡ r n ( mod  m n ) \begin{cases} x \equiv r_1 (\text{mod } m_1) \\ x \equiv r_2 (\text{mod } m_2) \\ \vdots \\ x \equiv r_n (\text{mod } m_n) \end{cases} xr1(mod m1)xr2(mod m2)xrn(mod mn)

思路是将任意整数 X X X 不断迭代成最终解。

这里补充一个方程: x ≡ r 0 ( mod  m 0 ) x \equiv r_0 (\text{mod } m_0) xr0(mod m0),其中 r 0 = 0 , m 0 = 1 r_0 = 0, m_0 = 1 r0=0,m0=1

设最终的通解为: r e t = x × m 0 + r 0 ret = x \times m_0 + r_0 ret=x×m0+r0,初始时 m 0 = 1 , r 0 = 0 m_0 = 1, r_0 = 0 m0=1,r0=0

然后依次迭代后面的方程,使的最终解 r e t ret ret 依次满足后续的方程。

设当前取的方程为: x ≡ r i ( mod  m i ) x \equiv r_i (\text{mod } m_i) xri(mod mi),即 r e t = y × m i + r i ret = y \times m_i + r_i ret=y×mi+ri

  1. 根据两式右边相等: r e t = x × m 0 + r 0 = y × m i + r i ret = x \times m_0 + r_0 = y \times m_i + r_i ret=x×m0+r0=y×mi+ri,

  2. 于是: x × m 0 − y × m i = r i − r 0 x \times m_0 - y \times m_i = r_i - r_0 x×m0y×mi=rir0,

  3. 形如 a x + b y = c ax + by = c ax+by=c,其中 a = m 0 , b = m i , c = r i − r 0 a = m_0, b = m_i, c = r_i - r_0 a=m0,b=mi,c=rir0

  4. 根据扩欧算法,可以解出特解 x 0 , y 0 x_0, y_0 x0,y0。(如果无解,算法结束)

  5. 通解为 x = x 0 + b d × k x = x_0 + \frac{b}{d} \times k x=x0+db×k,代入最终解中: r e t = ( x 0 + b d × k ) × m 0 + r 0 ret = (x_0 + \frac{b}{d} \times k) \times m_0 + r_0 ret=(x0+db×k)×m0+r0

  6. 整理得: r e t = k ( b d × m 0 ) + ( x 0 × m 0 + r 0 ) ret = k(\frac{b}{d} \times m_0) + (x_0 \times m_0 + r_0) ret=k(db×m0)+(x0×m0+r0),既满足之前的方程,也满足当前方程。

其中新的 m ′ = b d × m 0 , r ′ = x 0 × m 0 + r m' = \frac{b}{d} \times m_0, r' = x_0 \times m_0 + r m=db×m0,r=x0×m0+r

  1. 利用 r e t ret ret的表达式继续迭代下一个方程。直到迭代完所有的方程或某个方程无解为止。

例如:

{ x ≡ 2 ( mod  3 ) x ≡ 3 ( mod  5 ) x ≡ 2 ( mod  7 ) \begin{cases} x \equiv 2(\text{mod } 3) \\ x \equiv 3(\text{mod } 5) \\ x \equiv 2(\text{mod } 7) \end{cases} x2(mod 3)x3(mod 5)x2(mod 7)

补上一个方程 x ≡ 0 ( mod  1 ) x \equiv 0 (\text{mod } 1) x0(mod 1),最终解 r e t = x × 1 + 0 ret = x \times 1 + 0 ret=x×1+0

迭代第一个方程: r e t = y × 3 + 2 ret = y \times 3 + 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 \begin{cases}x=2+3k\\y=0-k\end{cases} {x=2+3ky=0k

  • 此时 r e t = k × 3 + 2 ret=k\times 3+2 ret=k×3+2 满足第一个方程。 k k k是整数,在后续迭代中也可看成新的未知数。

迭代第二个方程: 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 \begin{cases} x = 2 + 5 \times k \\ y = -1 - 3 \times k \end{cases} {x=2+5×ky=13×k

  • 于是将 x x x的通解代入 r e t ret ret 中得: r e t = k × 15 + 8 ret = k \times 15 + 8 ret=k×15+8。此时 r e t ret ret 满足前两个方程。

迭代第三个方程: 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 \begin{cases} x = -6 + 7 \times k \\ y = 12 - 15 \times k \end{cases} {x=6+7×ky=1215×k

  • 代入 r e t ret ret 中得: 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 为最小非负整数解。

扩展中国剩余定理OJ示例

之前的OJ例如P1495 【模板】中国剩余定理(CRT)/ 曹冲养猪 - 洛谷、[P3868 TJOI2009] 猜数字 - 洛谷也能用扩展中国剩余定理解决。

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

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

需要注意的点是,构造的方程 a x + b y = c ax+by=c ax+by=c c c c需要为最小整数,以及利用 a x + b y = gcd ( a , b ) ax+by=\text{gcd}(a,b) ax+by=gcd(a,b)求方程 a x + b y = c ax+by=c ax+by=c的特解时,需要使用龟速乘算法,这些都是为防止溢出需要做的操作。

#include<bits/stdc++.h>
using namespace std;typedef long long LL;LL exgcd(LL a, LL b, LL& x, LL& y) {if (!b) {x = 1; y = 0;return a;}LL x1, y1, d = exgcd(b, a % b, x1, y1);x = y1; y = x1 - a / b * y1;return d;
}LL qmul(LL a, LL b, LL m) {LL ans = 0;while (b) {if (b & 1)ans = (ans + a) % m;a = (a * 2) % m;b >>= 1;}return ans;
}void ac() {int n;cin >> n;vector<LL>a(n + 1, 0), b(a);for (int i = 1; i <= n; i++)cin >> a[i] >> b[i];//扩展中国剩余定理//ret = k*m+rLL m = 1, r = 0;for (int i = 1; i <= n; i++) {//解不定方程mx+a[i]y=b[i]-rLL x, y, d = exgcd(m, a[i], x, y), c = b[i] - r;c = (c % a[i] + a[i]) % a[i];if (c % d) {cout << -1 << endl;return;}y = a[i] / d;//y是不定方程的另一个解,不重要,于是代码复用//加龟速乘,擦边通关OJx = qmul(x, c / d, y);x = (x % y + y) % y;r = x * m + r;m = y * m;r = (r % m + m) % m;//ret = k*m+r,可以用m对r进行范围限制}cout << r << endl;
}int main() {ios::sync_with_stdio(false);cin.tie(0);int T = 1;//cin >> T;while (T--)ac();return 0;
}

NOI2018 屠龙勇士

[P4774 NOI2018] 屠龙勇士 - 洛谷

这种个人感觉是真正的压轴题或准压轴题级别的题,题目又臭又长,还得逐句分析,其中分析还得分阶段,严重的还有分类谈论,每个阶段的分析还得有联系,不然无法流畅地翻译成自然语言或代码。

在实现时往往伴随着知识点的嵌套使用,极其考验综合能力。

读题:

首先是游戏规则:

  1. 1条龙,打它会使Hp会变负数,然后回血,Hp恢复到0时龙死去,否则不能死去。

    例如龙的初始Hp为 5,将血量斩杀到 -4,龙的回血 p = 2 p=2 p=2,回 2 次后龙会死去;或刚好砍到Hp位0,则龙死去。

    但如果某几次攻击将血量斩杀到-3,龙回血2次后血量变成1,龙就还能蹦迪。

    所以选择的武器需要将龙的血量控制在 p p p的整数倍才能击杀。

  2. 砍龙需要选剑,砍死龙得新剑。

然后这人想写个脚本之类的东西挂机刷游戏,脚本原理:

  1. 选择当前有的、伤害不大于龙Hp、同时伤害是最大的剑。没有就选伤害最小的。

    例如Hp = 5,剑 a [ i ] = { 1 , 2 , 3 , 4 , 4 , 5 , 6 , 7 , 8 , 9 } a[i]=\{1,2,3,4,4,5,6,7,8,9\} a[i]={1,2,3,4,4,5,6,7,8,9},此时选a[5]=5的剑。

    a [ i ] = 6 , 7 a[i]={6,7} a[i]=6,7,此时选a[0]=6的剑。

  2. 对每条龙,设置固定的攻击次数 x x x

之后就是解题思路:

  1. 确定哪条龙用哪把剑。

    既然要选剑,则剑应该有序,想到排序

    对每条龙,考虑Hp和奖励,在剑的集合中选不大于龙的血量且伤害最大的,首先应该想到二分优化的枚举,二分的话要考虑数组,数组需要看数据量是否支持。

    因为有消耗和奖励,需要频繁插入和删除还有取出指定位置的剑,想到集合multiset,插入和取出都是 O ( log ⁡ n ) O(\log n) O(logn)。可以选择用upper_bound快速查找,但要注意这个成员函数找的是大于Hp的最小值,所以需要使返回的迭代器减1,边界情况是所有剑都大于Hp。

  2. 如何确定攻击次数 x x x

    根据题意解读的每条龙的信息{初始血量Hp,恢复rv,剑的伤害swd,打的次数x,恢复次数y},根据游戏规则构造等式 H p − s w d × x + r v × y = 0 Hp-swd\times x+rv\times y=0 Hpswd×x+rv×y=0,即 s w d × x = r v × y + H p swd\times x=rv\times y+Hp swd×x=rv×y+Hp,看到这个等式想到
    s w d × x ÷ r v = y … H p swd\times x\div rv=y\dots Hp swd×x÷rv=yHp,2个等式 s w d × x = r v × y + H p swd\times x=rv\times y+Hp swd×x=rv×y+Hp s w d × x ÷ r v = y … H p swd\times x\div rv=y\dots Hp swd×x÷rv=yHp中, y y y也是未知数,所以根据这2个等式可得出同余方程 s w d × x ≡ H p ( m o d r v ) swd\times x\equiv Hp\pmod {rv} swd×xHp(modrv)

    对所有龙的同余方程组

    { s w d 1 x ≡ H p 1 ( m o d r v 1 ) s w d 2 x ≡ H p 2 ( m o d r v 2 ) … s w d n x ≡ H p n ( m o d r v n ) \begin{cases}swd_1x\equiv Hp_1\pmod {rv_1}\\swd_2x\equiv Hp_2\pmod {rv_2}\\\dots\\swd_nx\equiv Hp_n\pmod {rv_n}\end{cases} swd1xHp1(modrv1)swd2xHp2(modrv2)swdnxHpn(modrvn)

    解出 x x x的,即为攻击次数。和之前的同余方程组不同,这个方程组的未知数带系数,使用扩展中国剩余定理时需要注意细节。

    • 扩展中国剩余定理解同余方程组:

      约定攻击次数 r e t ret ret,恢复力 m = r v m=rv m=rv r = H p r=Hp r=Hp

      设方程 r e t = k m + r ret=km+r ret=km+r m = 1 m=1 m=1 r = 0 r=0 r=0

      对任一方程 s w d × x ≡ H p ( m o d r v ) swd\times x\equiv Hp\pmod {rv} swd×xHp(modrv),其实就是 s w d × r e t = y × r v + H p swd\times ret=y\times rv+Hp swd×ret=y×rv+Hp,次数 x x x换成了 r e t ret ret,都表示攻击次数。

      联立:

      { r e t = k m + r s w d × r e t = y × r v + H p \begin{cases}ret=km+r\\swd\times ret=y\times rv+Hp\end{cases} {ret=km+rswd×ret=y×rv+Hp

      第1个方程左右同时乘 s w d swd swd消去 r e t ret ret,并移项得

      s w d × m × k + y × r v = H p − s w d × r swd\times m\times k+y\times rv=Hp-swd\times r swd×m×k+y×rv=Hpswd×r,此时得到一个不定方程

      对比 A x + B y = C Ax+By=C Ax+By=C A = s w d × m A=swd\times m A=swd×m B = r v B=rv B=rv C = H p − s w d × r C=Hp-swd\times r C=Hpswd×r

      之后就是熟悉的流程:扩展欧几里得算法 k k k(或者说 x x x)的通解,代入 r e t = k m + r ret=km+r ret=km+r得到新的 m m m r r r,并用它们继续迭代其他方程。

      对比之前的扩展中国剩余定理,多个常数系数时只需要联立方程组即可解决。

    • 细节问题:

      • 防溢出

        在乘法阶段用龟速乘算法代替。同时对方程 s w d × x ≡ H p ( m o d r v ) swd\times x\equiv Hp\pmod {rv} swd×xHp(modrv) s w d × x swd\times x swd×x H p Hp Hp可以都同时先模 m m m限制大小,即 ( s w d × x m o d r v ) ≡ ( H p m o d r v ) ( m o d r v ) (swd\times x\bmod rv)\equiv {(Hp\bmod rv)}\pmod {rv} (swd×xmodrv)(Hpmodrv)(modrv)。其他地方能取模尽量取模

      • 最终结果 r e t = k m + r ret=km+r ret=km+r中的 r r r可能不是正确答案,而是只是方程组的解,此时还需要 y > 0 y>0 y>0也就是恢复次数也要大于0。

        此时对于每条龙的血量 H p Hp Hp和剑的伤害 s w d swd swd,在求攻击次数时需为 ⌈ H p s w d ⌉ \lceil\frac{Hp}{swd}\rceil swdHp。例如Hp为5的龙,伤害为2的剑,攻击次数需要为 ⌈ 5 2 ⌉ = 3 \lceil\frac{5}{2}\rceil=3 25=3,才能压血线到负数。

        同时需要将每条龙斩到负数Hp的攻击次数并不完全相同,此时需要取最大的次数,取方程中攻击次数大于最大攻击次数的特解,才能保证将所有的龙的血量砍到负数。这里假设所有龙中需要被砍的最多的次数为 max_tm \text{max\_tm} max_tm max_tm = ⌈ Hp swd ⌉ \text{max\_tm}=\lceil\frac{\text{Hp}}{\text{swd}}\rceil max_tm=swdHp

        对表示攻击次数的方程 r e t = k m + r ret=km+r ret=km+r,需有 r e t ≥ max_tm ret\geq \text{max\_tm} retmax_tm

        • r ≥ max_tm r\geq \text{max\_tm} rmax_tm,此时 r e t = r ret=r ret=r

        • r < max_tm r<\text{max\_tm} r<max_tm,此时 r e t = ⌈ max_tm − r m ⌉ × m + r ret=\lceil\frac{\text{max\_tm}-r}{m}\rceil\times m+r ret=mmax_tmr×m+r

          这里 ⌈ max_tm − r m ⌉ \lceil\frac{\text{max\_tm}-r}{m}\rceil mmax_tmr表示需要在最小正整数解的基础上增加的模数 m m m的数量,这样做是为了求能以最低的固定攻击次数将所有的龙都压到负数Hp的特解。

参考程序(变量名和数组名与分析对应):

#include<bits/stdc++.h>
using namespace std;typedef long long LL;LL exgcd(LL a, LL b, LL& x, LL& y) {if (!b) {x = 1; y = 0;return a;}LL x1, y1, d = exgcd(b, a % b, x1, y1);x = y1; y = x1 - a / b * y1;return d;
}LL qmul(LL a, LL b, LL m) {LL ans = 0;while (b) {if (b % 2)ans = (ans + a) % m;a = (a * 2) % m;b /= 2;}return ans;
}void ac() {int N, M;cin >> N >> M;//Hp血量,rv恢复,rd reword奖励,swd sword砍龙时用的剑vector<LL>Hp(N + 1, 0), rv(Hp), rd(Hp),swd(Hp);multiset<LL>st;LL max_tm = 0;//max times,最难砍的龙需要的次数for (int i = 1; i <= N; i++)cin >> Hp[i];for (int i = 1; i <= N; i++)cin >> rv[i];for (int i = 1; i <= N; i++)cin >> rd[i];for (int i = 1; i <= M; i++) {LL x; cin >> x;st.insert(x);}//选择剑for (int i = 1; i <= N; i++) {//二分auto it = st.upper_bound(Hp[i]);if (it != st.begin())--it;swd[i] = *it;max_tm = max(max_tm, (Hp[i] + swd[i] - 1) / swd[i]);st.erase(it);st.insert(rd[i]);}//确定攻击次数//扩展中国剩余定理,初始方程ret=mx+r,m是rv,r是hp,res是攻击次数//对应同余方程(组)swd*x % rv = HpLL m = 1, r = 0;for (int i = 1; i <= N; i++) {//Hp-swd*x+rv*y=0 得到同余方程//联立ret=mx+r和Hp-swd*x+rv*y=0//得到不定方程 (swd-m) x + rv* y= Hp-swd*rLL A = qmul(swd[i] ,m,rv[i]), B = rv[i], C = Hp[i] - swd[i] * r;C = (C % B + B) % B;LL x, y, d = exgcd(A, B, x, y);if (C % d) {cout << -1 << endl;return;}LL g1 = B / d;x = qmul(x,C / d,g1);x = (x % g1 + g1) % g1;r = r + x * m;m = g1 * m;r = (r % m + m) % m;}//最小正整数解可能无法将所有的龙都砍到负数Hp需要求特解if (r < max_tm)r = r + (max_tm - r + m - 1) / m * m;cout << r<<endl;
}int main() {ios::sync_with_stdio(false);cin.tie(0);int T = 1;cin >> T;while (T--)ac();return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.pswp.cn/news/908388.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Python爬虫实战:研究MechanicalSoup库相关技术

一、MechanicalSoup 库概述 1.1 库简介 MechanicalSoup 是一个 Python 库,专为自动化交互网站而设计。它结合了 requests 的 HTTP 请求能力和 BeautifulSoup 的 HTML 解析能力,提供了直观的 API,让我们可以像人类用户一样浏览网页、填写表单和提交请求。 1.2 主要功能特点…

㊗️高考加油

以下是极为详细的高考注意事项清单&#xff0c;涵盖考前、考中、考后全流程&#xff0c;建议逐条核对&#xff1a; 一、考前准备 1. 证件与物品 必带清单&#xff1a; 准考证&#xff1a;打印2份&#xff08;1份备用&#xff09;&#xff0c;塑封或夹在透明文件袋中防皱湿。身…

学习路之PHP--webman安装及使用、webman/admin安装

学习路之PHP--webman安装及使用、webman/admin安装 一、安装webman二、运行三、安装webman/admin四、效果五、配置Nginx反向代理&#xff08;生产环境&#xff1a;可选&#xff09;六、win10运行问题集七、使用 一、安装webman 准备&#xff1a; PHP > 8.1 Composer > 2…

mamba架构和transformer区别

Mamba 架构和 Transformer 架构存在多方面的区别&#xff0c;具体如下&#xff1a; 计算复杂度1 Transformer&#xff1a;自注意力机制的计算量会随着上下文长度的增加呈平方级增长&#xff0c;例如上下文增加 32 倍时&#xff0c;计算量可能增长 1000 倍&#xff0c;在处理长序…

Python爬虫实战:研究mechanize库相关技术

1. 引言 随着互联网数据量的爆炸式增长,网络爬虫已成为数据采集和信息挖掘的重要工具。Python 作为一种功能强大且易于学习的编程语言,拥有丰富的爬虫相关库,如 Requests、BeautifulSoup、Scrapy 等。Mechanize 库作为其中的一员,特别擅长处理复杂的表单提交和会话管理,为…

如何使用索引和条件批量更改Series数据

视频演示 如何通过索引与布尔条件修改 pandas Series&#xff1f;实操演示来了 一、前言&#xff1a;掌握Series数据修改是数据处理的基础 在使用Python进行数据分析时&#xff0c;Pandas库的Series对象是最常用的结构之一。在上一个视频中我们已经学习了如何创建Series对象&a…

CentOS 7 如何安装llvm-project-10.0.0?

CentOS 7 如何安装llvm-project-10.0.0&#xff1f; 需要先升级gcc至7.5版本&#xff0c;详见CentOS 7如何编译安装升级gcc版本?一文 # 备份之前的yum .repo文件至 /tmp/repo_bak 目录 mkdir -p /tmp/repo_bak && cd /etc/yum.repo.d && /bin/mv ./*.repo …

6个月Python学习计划 Day 15 - 函数式编程、高阶函数、生成器/迭代器

第三周 Day 1 &#x1f3af; 今日目标 掌握 Python 中函数式编程的核心概念熟悉 map()、filter()、reduce() 等高阶函数结合 lambda 和 列表/字典 进行数据处理练习了解生成器与迭代器基础&#xff0c;初步掌握惰性计算概念 &#x1f9e0; 函数式编程基础 函数式编程是一种…

SpringCloud Gateway 集成 Sentinel 详解 及实现动态监听Nacos规则配置实时更新流控规则

目录 一、前言二、版本选择和适配 2.1、本文使用各组件版本2.2、官方推荐版本 三、部署sentinel-dashboard 3.1、下载 sentinel-dashboard jar包3.2、启动 sentinel-dashboard 四、Gateway 集成 Sentinel实现控制台配置流控规则测试 4.1、添加Gateway 集成 Sentinel 包4.2、添加…

Linux八股【1】-----虚拟内存

参考&#xff1a;小林coding 虚拟内存存在的目的&#xff1f; 为了能够同时运行多个进程同时进程之间互不干扰 虚拟地址通过MMU找到物理地址 物理内存怎么映射的&#xff1f; 物理内存的映射方法主要有两种&#xff0c;内存分段和内存分页 内存分段 把程序的不同区&#…

惊艳呈现:探索数据可视化的艺术与科学

一张图表真能胜过千言万语&#xff1f;当超市销售数据变成跳动的热力图&#xff0c;当城市交通拥堵状况化作流动的光带&#xff0c;数据可视化正以超乎想象的方式重塑我们认知世界的维度。但你是否想过&#xff0c;那些看似精美直观的图表背后&#xff0c;藏着怎样精密的科学逻…

06-排序

排序 1. 排序的概念及其应用 1.1 排序的概念 排序&#xff1a;所谓排序&#xff0c;就是使一串记录&#xff0c;按照其中的某个或某些关键字的大小&#xff0c;递增或递减的排列起来的操作。 稳定性&#xff1a;假定在待排序的记录序列中&#xff0c;存在多个具有相同的关键…

从失效文档到知识资产:Gitee Wiki 引领研发知识管理变革

在关键领域软件研发的复杂生态中&#xff0c;知识管理正成为制约行业发展的关键瓶颈。随着软件系统规模不断扩大、技术栈日益复杂&#xff0c;传统文档管理模式已难以满足现代软件工厂对知识沉淀、共享和传承的需求。Gitee Wiki作为新一代知识管理平台&#xff0c;通过技术创新…

MySQL 性能调优入门 - 慢查询分析与索引优化基础

MySQL 性能调优入门 - 慢查询分析与索引优化基础 性能问题诊断的通用思路 当数据库出现性能问题时,切忌盲目猜测或随意调整参数。一个科学的诊断流程通常包括: 基于数据,而非猜测 (Data-Driven, Not Guesswork):利用我们在上一篇讨论的性能监控指标和建立的基线。查看哪些…

8天Python从入门到精通【itheima】-73~74(数据容器“集合”+案例练习)

目录 73节-集合的基础定义和操作 1.学习目标 2.为什么要用集合 3.集合的定义 4.关于集合的常用操作 【1】添加新元素&#xff1a;add方法 【2】移除元素&#xff1a;remove方法 【3】随机取出元素&#xff1a;pop方法 【4】清空集合&#xff1a;clear方法 【5】取出两…

国芯思辰| AD7894的优质替代方案:SC1424模数转换器在分布式控制系统中的应用优势

分布式控制系统将控制任务分散至多个节点&#xff0c;各节点协同工作以实现复杂的控制目标。在这一架构下&#xff0c;系统ADC提出了严苛要求。高精度是精准采集各类模拟信号&#xff08;如传感器输出的电压、电流信号&#xff09;的基础&#xff0c;关乎控制决策的准确性&…

Unity基础-数学向量

Unity基础-数学向量 二、向量相关用法 概述 向量在Unity游戏开发中扮演着重要角色&#xff0c;用于表示位置、方向、速度等。Unity提供了Vector2、Vector3等结构体来处理向量运算。 1. 向量基础操作 1.1 向量创建和访问 // 创建向量 Vector3 position new Vector3(1, 2,…

Neo4j 数据建模:原理、技术与实践指南

Neo4j 作为领先的图数据库,其核心优势在于利用图结构直观地表达和高效地查询复杂关系。其数据建模理念与传统关系型数据库截然不同,专注于实体(节点)及其连接(关系)。以下基于官方文档,系统阐述其建模原理、关键技术、实用技巧及最佳实践: 一、 核心原理:以关系为中心…

volka 25个短语动词

以下是分句分段后的内容&#xff1a; 3,000. Thats 95% of spoken English. And I am teaching you all of these words. First, Ill teach you todays words. And then youll hear them in real conversations. With my brother. Stick around until the end, because witho…

服务器中日志分析的作用都有哪些

服务器日志是用来检测和排查可疑行为的主要工具&#xff0c;运维团队可以通过分析和解读日志文件&#xff0c;发现服务器中潜在的网络安全威胁或异常活动&#xff0c;下面&#xff0c;就让小编和大家一起来了解一下服务器中日志分析的作用都有什么吧&#xff01; 对于服务器中的…