##[$AcWing$ $875$. 快速幂](https://www.acwing.com/problem/content/877/) ### 一、题目描述 给定 $n$ 组 $a_i,b_i,p_i$,对于每组数据,求出 $a_i^{b_i}~mod~p_i$ 的值。 **输入格式** 第一行包含整数 $n$。 接下来 $n$ 行,每行包含三个整数 $a_i,b_i,p_i$。 **输出格式** 对于每组数据,输出一个结果,表示 $a^{b_i}~mod~p_i$ 的值。 每个结果占一行。 **数据范围** $1≤n≤100000, 1≤a_i,b_i,p_i≤2×10^9$ ### 二、快速幂原理 答:通过将指数拆分成几个因数相乘的形式,来简化幂运算。在计算$3^{13}$ 的时候,普通的幂运算算法需要计算$13$次,但是如果我们将它拆分成$3^{8+4+1}$ ,再进一步拆分成 只需要计算$4$次。嗯?哪来的$4$次?,别急,接着看。 这种拆分思想其实就是借鉴了二进制与十进制转换的算法思想(**倍增**),我们知道$13$的二进制是$1101$,可以知道: $13=1×2^3 + 1×2^2 + 0×2^1 + 1×2^0 = 8 + 4 + 1$ 原理就是利用位运算里的位移“>>”和按位与“&”运算,代码中$k\&1$其实就是取$k$二进制的最低位,用来判断最低位是$0$还是$1$,再根据是$0$还是$1$决定乘不乘,不理解的话联系一下二进制转换的过程。 $k >>= 1$其实就是将k的二进制向右移动一位,就这样位移、取最低位、位移、取最低位,这样循环计算,直到指数$k$为$0$为止,整个过程和我们手动将二进制转换成十进制是非常相似的。 普通幂算法是需要循环指数次,也就是指数是多少就要循环计算多少次,而快速幂因为利用了位移运算,只需要算“**指数二进制位的位数**”次,对于$13$来说,二进制是$1101$,有$4$位,就只需要计算$4$次,快速幂算法时间复杂度是$O(logn)$级别,对于普通幂需要计算一百万次的来说,快速幂只需要计算$6$次,这是速度上质的飞跃,但是需要多注意溢出的问题。 ### 三、简单粗暴快速幂 可用于结合高精度乘法 ```cpp {.line-numbers} #include using namespace std; int qmi(int a, int k) { int res = 1; while (k) { if (k & 1) res = res * a; //假设乘法不会造成溢出 k >>= 1; a = a * a; //假设乘法不会造成溢出 } return res; } int main() { printf("%d",qmi(3,4)); return 0; } ``` ### 四、带取模的快速幂 ```cpp {.line-numbers} #include using namespace std; #define int long long int n; int qmi(int a, int k, int p) { int res = 1; while (k) { if (k & 1) res = res * a % p; k >>= 1; a = a * a % p; } return res; } signed main() { cin >> n; while (n--) { int a, k, p; cin >> a >> k >> p; printf("%d\n", qmi(a, k, p)); } return 0; } ``` ### 五、龟速乘 #### 题目描述 ($64$位整数乘法)求 $a$ 乘 $b$ 对 $p$ 取模的值。 **输入格式** 第一行输入整数$a$,第二行输入整数$b$,第三行输入整数$p$。 **输出格式** 输出一个整数,表示 $a*b$ $mod$ $p$的值。 **数据范围** $1 ≤ a , b , p ≤ 10^{18}$ **输入样例** ```cpp {.line-numbers} 3 4 5 ``` **输出样例** ```cpp {.line-numbers} 2 ``` #### 算法思想 二进制思想。如果直接计算$a × b$这会爆 $long$ $long$ ,所以采用 **类似于快速幂的思想** 把 $b$作为二进制形式进行处理,然后如果某位上为$1$就加上它$a × 2$次方,并且每次计算后取模就可以了。 例如:$b=11=(1011)_2=2^3+2^1+2^0$,那么$a × b = a × ( 2^3 + 2^1 + 2^0 ) = 8a + 2a + a$。 #### 时间复杂度 将乘数$b$的每个二进制位取出进行判断,时间复杂度为$log(b)$。 #### 代码实现 ```cpp {.line-numbers} #include using namespace std; #define int long long // 龟速乘 int gui(int a, int b, int p) { int res = 0; while (b) { if (b & 1) res = (res + a) % p; a = (a + a) % p; b >>= 1; } return res; } int main() { int a, b, p; cin >> a >> b >> p; printf("%lld\n", gui(a, b, p)); return 0; } ```