hitoli717

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

(自用)CSP-J-2022-2 解密(附一个额外的知识点之俄式乘法/俄国农夫算法)

题目描述

给定一个正整数 \(k\),有 \(k\) 次询问,每次给定三个正整数 \(n_i, e_i, d_i\),求两个正整数 \(p_i, q_i\),使 \(n_i = p_i × q_i, e_i × d_i = (p_i − 1)(q_i − 1) + 1\)

输入输出

输入

第一行一个正整数 k,表示有 k 次询问。
接下来 k 行,第 i 行三个正整数 ni, di, ei。

输出

输出 k 行,每行两个正整数 \(p_i, q_i\) 表示答案。为使输出统一,你应当保证 \(p_i ≤ q_i\)
如果无解,请输出 NO。

样例

样例输入
10
770 77 5
633 1 211
545 1 499
683 3 227
858 3 257
723 37 13
572 26 11
867 17 17
829 3 263
528 4 109
样例输出
2 385
NO
NO
NO
11 78
3 241
2 286
NO
NO
6 88

数据范围

以下记 $ m = n − e × d + 2$。
保证对于 100% 的数据,\(1 ≤ k ≤ 10^5\),对于任意的 \(1 ≤ i ≤ k\)\(1 ≤ n_i ≤ 10^{18}\), \(1 ≤ e_i × d_i ≤ 10^{18}, 1 ≤ m ≤ 10^9\)

测试点编号 k ≤ n ≤ m≤ 特殊性质
1 \(10^3\) \(10^3\) \(10^3\) 保证有解
2 \(10^3\) \(10^3\) \(10^3\)
3 \(10^3\) \(10^9\) \(4×10^6\) 保证有解
4 \(10^3\) \(10^9\) \(4×10^6\)
5 \(10^3\) \(10^9\) \(10^9\) 保证有解
6 \(10^3\) \(10^9\) \(10^9\)
7 \(10^5\) \(10^{18}\) \(10^9\) 保证若有解则 \(p=q\)
8 \(10^5\) \(10^{18}\) \(10^9\) 保证有解
9 \(10^5\) \(10^{18}\) \(10^9\)
10 \(10^5\) \(10^{18}\) \(10^9\)

题解

看到数据规模到了 \(10^{18}\) , \(O(n)\) 直接宣告死刑了。就要考虑直接根据规律 \(O(1)\) 出结果,或者再差也得是用得到折半之类的 \(O(log\ n)\)
再就是\(n,\ e_i*d_i\) 都是 \(10^{18}\) 规模的正整数,肯定是要unsigned long long int

O(√n)解法(TLE了)

枚举n的所有因子不需要从1遍历到n,只需要从1遍历到 \(\sqrt n\) 就行。初始令p=1,循环终止条件是 \(p*p≤n\) ,然后枚举可行的 \(p,q\) ,即令 \(q=n/p\)\(p*q\) 是否等于 \(n\) 。时间复杂度 \(O(\sqrt n)\),但还是TLE

二分查找p

根据 \(n = p × q,\ e × d = (p − 1)(q − 1) + 1\) ,得 $p + q = n - e × d + 2 = n',\ p,q≥1 $ ,并且由于总要保持 \(p ≤ q\)。所以我们折半地枚举 \([1,\frac{n'}{2}]\) 中所有 \(p,q=n'-p\) 。由均值定理 \(p,q\) 越接近它们的积越大,所以 \(pq<n\) 时更新左界,反之更新右界(或者用 \(e × d = (p − 1)(q − 1) + 1\) 当判定条件也行)

俄式除法/俄国农夫算法

之所以要提一嘴这个算法是因为一个我看错了数据范围导致的错误思路,误以为 \(e,d\) 都是 \(10^{18}\) 规模,发现 \(e×d\)uint64_t装不下了,又看到 \(e×d = (p − 1)(q − 1) + 1\) ,以为要对 \(n-p-q+1\) 取模为1判断是否可以。就找了一下两个大数相乘可能会溢出的情况下怎么求模运算。
简单来说,对乘式 \(A*B\) ,俄式乘法就是把乘数 \(B\) 按照二进制位权分解为 \(b_n...b_1b_0,\ b_i=0或1\) ,将 \(A*B\) 分解为 \(\sum_{i=0}^{n} (A*2^i*b_i)\)。再根据如下两个公式
\((A*B) \% C = ((A\%C) * (B\%C)) \% C\)
\((A+B) \% C = ((A\%C) + (B\%C)) \% C\)
我们将A,B对C取模后的A',B'作为新的乘数,计算它们的积对C取模,这两个乘数仍然有可能溢出,我们用俄式乘法分解成求和式取模,即%(A'*B')%C = \((\sum_{i=0}^{n} (A*2^i*b_i) \ \%C)\ \%C\)
我们用res记录最终的结果,每次将 \(B\) 的最低位移出并判断,如果为1,那么res就需要加上 \(A\) 并再次对C取模作为暂时的结果。然后我们需要将 \(A<<1\) 并对C取模,这是为了更新\((\sum_{i=0}^{n} (A*2^i*b_i \ \%C))\ \%C\) 每一项的值。这样我们每次只需要res = (res+A)%C即可

posted on 2024-12-21 17:42  Hitoli  阅读(78)  评论(0)    收藏  举报