P4777 【模板】扩展中国剩余定理(EXCRT) 题解

· · 题解

题目大意

求解同余方程组:

\begin{cases} x \equiv b_1\ ({\rm mod}\ a_1) \\ x\equiv b_2\ ({\rm mod}\ a_2) \\ ... \\ x \equiv b_n\ ({\rm mod}\ a_n)\end{cases} ## 题目分析 膜拜大佬 [Katyusha_01](https://www.luogu.com.cn/user/533742) 提供的宝贵思路,经同意转述。 我们尊敬的阮老师在题解中提到: ![](https://cdn.luogu.com.cn/upload/image_hosting/688wbqro.png) 题解区现有的 $24$ 篇题解没有一篇对这段话提出质疑。 给它动小手术是没有用的,吗? 既然都叫 EXCRT 了,我们就要由 CRT 扩展出新的做法! 大佬的第一步是,既然模数不互质,那么我们就想办法拆方程,将模数转化为互质的,就可以使用 CRT 了。 我们能想到的唯一方法就是质因数分解了。我们有以下结论: > 同余方程 $x \equiv b\ ({\rm mod}\ a)$ 等价于方程组 $\begin{cases} x \equiv b\ ({\rm mod}\ p_1^{k_1}) \\ x\equiv b\ ({\rm mod}\ p_2^{k_2}) \\ ... \\ x \equiv b\ ({\rm mod}\ p_m^{k_m})\end{cases}$。其中 $p_i^{k_i}$ 是对 $a$ 质因数分解的结果。 这个结论在 exLucas 中有典型的应用,证明非常简单,无需赘述。 在此题中,有 $a_i$ 的 $\operatorname{lcm}$ 小于等于 $10^{18}$,设其为 $V$,故将所有方程分解后的 $p_i$ 的种类不会超过 $\log V$。所以我们可以遍历 $a$,两步分解质因数:第一步,使用之前分解出的质数来尝试分解,复杂度 $O(\log V)$。第二步,将第一步分解完后剩下的 $a_i$ 暴力分解,这一步单次 $O(\sqrt {a_i})$,但只会执行 $O(\log V)$ 次,在此题复杂度正好足够。 对于分解出来的质数 $p_i$,可能有多个对应的 $k_i$ 的模数,它们并不互质,但没关系,我们还有一个性质。 > 对于质数 $p$,正整数 $k_1<k_2$,若 $x \equiv b\ ({\rm mod}\ p^{k_2})$,则 $x \equiv b\ ({\rm mod}\ p^{k_1})$。 这个性质可以理解为,$x$ 在 $p$ 进制下,$x \equiv b\ ({\rm mod}\ p^{k})$ 相当于限制了 $x$ 的后 $k$ 位需要和 $b$ 相同,所以 $k$ 大时限制更强。 所以对于单个质数 $p_i$,我们只需要保留限制最强的也就是最大的 $k_i$ 对应的方程,如果这个方程和其它方程有矛盾,则说明方程组无解。 这样留下来的就是若干个不同的质数的次幂作为模数,它们互质,这时候就可以放心的使用 CRT 求解了!! 复杂度 $O(\sqrt{V_1}\log V_2+\log^2 V_2)$,其中 $V_1$ 为模数的值域,$V_2$ 为模数的 $\operatorname{lcm}$ 的值域,瓶颈在于质因数分解,如果使用高级的分解质因数算法,这个方法的使用范围可能能广一点。 我们不仅对 CRT 动了小手术,还对它动了两次手术!!! 经本人允许,代码也给出 Katyusha_01 的代码。 ```cpp #include<bits/stdc++.h> using namespace std; void exgcd(long long a,long long b,long long& x,long long& y) { if(!b) return x = 1,y = 0,void(); exgcd(b,a % b,y,x),y -= a / b * x; } long long ny(long long a,long long mod) { a %= mod; long long x,y; exgcd(a,mod,x,y); return (x % mod + mod) % mod; } int n; long long a[100011],b[100011]; long long p[111],mod[111],rem[111],idx; void Insert(long long& x,long long y,int i) { long long nmod = 1; while(x % p[i] == 0) x /= p[i],nmod *= p[i]; if(nmod > mod[i]) mod[i] = nmod,rem[i] = y % nmod; } int main() { ios::sync_with_stdio(false); cin.tie(0),cout.tie(0); cin >> n; for(int i = 1;i <= n;i++) { cin >> a[i] >> b[i]; for(int j = 1;j <= idx;j++) Insert(a[i],b[i],j); if(a[i] > 1) { for(long long np = 2;np * np <= a[i];np++) if(a[i] % np == 0) p[++idx] = np,Insert(a[i],b[i],idx); if(a[i] > 1) p[++idx] = a[i],Insert(a[i],b[i],idx); } } long long M = 1; for(int i = 1;i <= idx;i++) M *= mod[i]; long long ans = 0; for(int i = 1;i <= idx;i++) (ans += (__int128)(M / mod[i]) * ny(M / mod[i],mod[i]) % M * rem[i] % M) %= M; cout << ans; return 0; } ```