---
# System prepended metadata

title: CSES Problem Set — Exponentiation 題解
tags: [CSES題解]

---

CSES Problem Set — Exponentiation 題解
===

題目
---
你的任務是有效率地算出 $a^b$ 模 $10^9+7$ 的值。

注意到在這題我們令 $0^0 = 1$。

### 輸入
- 第一行輸入一個整數 $n$，代表計算的個數。（$1 \le n \le 2 \cdot 10^5$）
- 接著有 $n$ 行，每一行有兩個整數 $a$、$b$。（$0 \le a,b \le 10^9$）

### 輸出
輸出 $a^b$ 模 $10^9+7$ 的值。


範例測資
---

```
Input :
3
3 4
2 8
123 123

Output :
81
256
921450052
```

想法：快速冪
---
雖然數學告訴我們 $a^b = \exp(b \cdot \log a)$，但機器實現的浮點數運算總有誤差，並不能完美符合數學公式，所以這題我們必須使用整數運算老老實實地乘出結果。

另一方面，這題的 $a$ 與 $b$ 最大都可能到 $10^9$ 這麼多，表示 $a^b$ 的實際值非常有可能超出程式容許的整數上限，先算出 $a^b$ 後再取模也是不實際的。所以我們必須利用以下的數論事實：\\[
  (x \times y) \bmod m = ((x \bmod m) \times (y \bmod m)) \bmod m.
\\] 也就是說我們可以在每次乘法之前就先對要運算的數值取模，這樣可以避免運算過程中數值超出限制。

雖然說我們要老老實實地乘出結果，但連續乘 $a$ 乘 $b$ 次（並且取模）並不是最有效率的方式。為了提升運算的效率，我們可以用俗稱「快速冪」的演算法，它只有 $O(\log b)$ 的時間複雜度，比起 $O(b)$ 的直接連乘快上許多。

快速冪只適用於滿足結合律的「乘法」運算 $x \otimes y$。這題的「乘法」可以定義成我們需要的 $(x \times y) \bmod m$，我們可以驗證說這個「乘法」的確滿足結合律。

因為快速冪有不少種寫法，下面的範例程式碼先省略不定義 `fastpow`，後面再把幾種寫法列出來。

### 範例程式碼

```cpp=
#include <iostream>
using namespace std;

int MOD = 1000000007;
int fastpow(int x, int n, int mod);

int main() {
    int n, a, b;
    cin >> n;
    for (int i = 0; i < n; i++) {
      cin >> a >> b;
      cout << fastpow(a, b, MOD);
    }    
}
```

### 快速冪核心概念

考慮一個滿足結合律的「乘法」運算 $\otimes$，並且把 $n$ 個 $x$「乘」在一起用 $x^n$ 表示。滿足結合律的意思是連續運算時括號順序並不影響結果：\\[
  (x \otimes y) \otimes z = x \otimes (y \otimes z).
\\] 而因為「乘法」滿足結合律，所以「次方」也會滿足指數律：\\[
  x^{m+n} = x^m \otimes x^n,\quad
  x^{mn} = (x^m)^n.
\\] 快速冪的核心概念就是用指數律來降低「乘法」的次數。我們通常會使用 $2$ 來除，比如說把 $x^{14}$ 變成 $(x^7)^2$ 或 $(x^2)^7$。像這樣一直遞迴下去，我們就能用 $O(\log n)$ 次的「乘法」算出 $x^n$。

下面根據拆法的不同分為兩種。

### 快速冪 1：$x^{2k} = (x^k)^2$
例如 $x^{14} = ((x^2 \otimes x)^2 \otimes x)^2$。

這裡使用以下的規則：\\[
  x^n = \begin{cases}
    1, & n = 0;\\
    x^k \otimes x^k, & n = 2k;\\
    x^k \otimes x^k \otimes x, & n = 2k+1.
  \end{cases}
\\] 並使用遞迴來實現。

> 如果「乘法」沒有單位元素 $x^0$，要把初始條件改成 $n = 1$ 時回傳 $x$。

```cpp=13
int fastpow(int x, int n, int mod) {
    if (n == 0)
        return 1;
    int r = fastpow(x, n/2);
    r = (r * r) % mod;
    if (n % 2)
        r = (r * x) % mod;
    return r;
}
```

### 快速冪 2：$x^{2k} = (x^2)^k$
例如 $x^{14} = x^8 \otimes x^4 \otimes x^2$，其中 $x^{2n}$ 都是從已經算過的 $x^n$「平方」之後算出來的。

這裡使用以下的規則： \\[
  x^n = \begin{cases}
    1, & n = 0;\\
    (x \otimes x)^k, & n = 2k;\\
    (x \otimes x)^k \otimes x, & n = 2k+1.
  \end{cases}
\\] 並使用遞迴來實現。

> 如果「乘法」沒有單位元素 $x^0$，要把初始條件改成 $n = 1$ 時回傳 $x$。

```cpp=13
int fastpow(int x, int n, int mod) {
    if (n == 0)
        return 1;
    int r = fastpow((x * x) % mod, n/2);
    if (n % 2)
        r = (r * x) % mod;
    return u;
}
```

我們也可以寫成迴圈。這個順序恰好跟遞迴版本顛倒，以 $x^7$ 為例，上面的遞迴版會算 $x^4 \otimes x^2 \otimes x$，而下面的迴圈版是算 $x \otimes x^2 \otimes x^4$。

```cpp=13
int fastpow(int x, int n, int mod) {
    int r = 1;
    while (n) {
        if (n % 2)
            r = (r * x) % mod;
        x = (x * x) % mod;
        n /= 2;
    }
    return r;
}
```
