Lũy thừa nhị phân
Giới thiệu
Lũy thừa nhanh (fast exponentiation), còn gọi là nhị phân lũy thừa (binary exponentiation) hay bình phương lũy thừa (exponentiation by squaring), là một kỹ thuật tính \(a^n\) trong \(\Theta(\log n)\) thời gian, trong khi cách vét cạn cần \(\Theta(n)\).
Kỹ thuật này áp dụng cho mọi bối cảnh mà phép nhân của \(a\) thỏa mãn tính kết hợp, như lũy thừa modulo, lũy thừa ma trận, v.v., xem thêm ở mục Ứng dụng.
Quy trình
Tính \(a^n\) tức là nhân \(n\) lần \(a\): \(a^{n} = \underbrace{a \times a \cdots \times a}_{n\text{ lần a}}\). Tuy nhiên khi \(n\) lớn hoặc phép nhân tốn kém, cách này không phù hợp. Ý tưởng của nhị phân lũy thừa là chia nhỏ bài toán theo biểu diễn nhị phân của số mũ.
Ví dụ
Cần tính \(3^{13}\). Nếu nhân liên tiếp thì cần \(13-1=12\) phép nhân. Nhưng vì
nên chỉ cần tính nhanh \(3^{1},3^{2},3^{4},3^{8}\) rồi nhân lại, mất \(2\) phép nhân để ra \(3^{13}\). Ta chỉ cần cách tính nhanh dãy các lũy thừa \(3^{2^k}\). Điều này dễ vì mỗi phần tử (trừ phần tử đầu) là bình phương của phần tử trước.
Quá trình tính \(3^{13}\):
Quá trình chỉ thực hiện \(5\) phép nhân.
Đó là ý tưởng cơ bản. Có hai cách cài đặt thường gặp.
Phiên bản lặp
Gọi biểu diễn nhị phân của \(n\) là \((n_tn_{t-1}\cdots n_1n_0)_2\), tức
với \(n_i\in\{0,1\}\). Khi đó
Chỉ các hạng với \(n_i=1\) mới xuất hiện trong tích.
Từ biểu thức này, ta có thể tính \(a^{2^k}\) trong \(\Theta(\log n)\) thời gian, rồi nhân các lũy thừa tương ứng với bit bằng \(1\) để ra kết quả trong \(\Theta(\log n)\) thời gian. Đó là phiên bản lặp.
Mã giả:
Cách này cần \(\Theta(\log n)\) phép nhân.
Phiên bản đệ quy
Quy trình cũng có thể viết đệ quy. Lưu ý biểu diễn nhị phân của \(n\) có thể viết:
Do đó
Mã giả:
Cách này đệ quy \(\Theta(\log n)\) lần và cũng cần \(\Theta(\log n)\) phép nhân. Do overhead đệ quy, thực tế phiên bản lặp thường nhanh hơn.
Ứng dụng
Lũy thừa modulo
洛谷 P1226【模板】快速幂
Cho ba số nguyên \(a,b,p\), tính \(a^b\bmod p\) với \(p\ge 2\).
Đây là ứng dụng rất phổ biến, ví dụ để tính nghịch đảo nhân modulo. Vì phép lấy modulo không ảnh hưởng đến phép nhân, chỉ cần lấy modulo trong quá trình tính.
Ta có thể cài đặt trực tiếp theo bản đệ quy:
Cài đặt tham khảo
1 2 3 4 5 6 7 8 | |
1 2 3 4 5 6 7 8 | |
Cách thứ hai là không đệ quy. Trong vòng lặp, ta nhân vào kết quả khi bit nhị phân bằng 1. Dù độ phức tạp lý thuyết giống nhau, cách này thường nhanh hơn vì không có overhead đệ quy.
Cài đặt tham khảo
1 2 3 4 5 6 7 8 9 | |
1 2 3 4 5 6 7 8 | |
Lưu ý
- Thông thường mô-đun lớn hơn \(1\). Trong trường hợp rất đặc biệt \(p=1\), cần xét riêng trường hợp \(b=0\).
- Khi số mũ rất lớn, cần dùng định lý Euler mở rộng để giảm mũ.
Tính số Fibonacci
Theo truy hồi \(F_n = F_{n-1} + F_{n-2}\), ta có thể xây dựng ma trận \(2\times 2\) biểu diễn biến đổi từ \(F_i,F_{i+1}\) sang \(F_{i+1},F_{i+2}\). Khi tính ma trận này mũ \(n\), áp dụng lũy thừa nhanh sẽ cho kết quả trong \(\Theta(\log n)\). Xem thêm dãy Fibonacci và tăng tốc truy hồi bằng ma trận.
Lặp hoán vị nhiều lần
Mô tả bài toán
Cho một dãy độ dài \(n\) và một hoán vị, áp dụng hoán vị đó \(k\) lần.
Chỉ cần lấy lũy thừa \(k\) của hoán vị rồi áp dụng lên dãy. Độ phức tạp \(O(n \log k)\). Xem thêm hợp thành hoán vị.
Lưu ý
Xây dựng đồ thị của hoán vị và xử lý từng chu trình với \(k\) lần (tương đương \(k\) modulo độ dài chu trình) sẽ giải trong \(O(n)\).
Tăng tốc thao tác hình học trên tập điểm
HDU 4087 A Letter to Programmers
Cho \(n\) điểm \(p_i\) trong không gian 3D, áp dụng \(m\) thao tác, gồm:
- Dời điểm theo một vector (Shift).
- Co/giãn theo tỉ lệ (Scale).
- Quay quanh một đường thẳng (Rotate).
Còn có thao tác đặc biệt lặp lại một chuỗi thao tác \(k\) lần (Repeat), Repeat có thể lồng nhau. Yêu cầu tọa độ cuối cùng.
Tham khảo Vector và ma trận. Mỗi thao tác là một ma trận biến đổi, chuỗi thao tác là tích ma trận. Repeat tương đương lấy lũy thừa ma trận. Nhờ đó tính ma trận tổng hợp trong \(O(m \log k)\), rồi áp dụng lên \(n\) điểm, tổng \(O(n + m \log k)\).
Đếm đường đi độ dài cố định
Mô tả bài toán
Cho đồ thị có hướng (trọng số cạnh bằng 1), đếm số đường đi độ dài \(k\) từ \(u\) đến \(v\) cho mọi cặp.
Lấy ma trận kề \(M\) mũ \(k\), khi đó \(M_{i,j}\) là số đường đi độ dài \(k\) từ \(i\) đến \(j\). Độ phức tạp \(O(n^3 \log k)\). Xem Ma trận.
Nhân số nguyên modulo
Mô tả bài toán
Cho \(a,b\) không âm và \(m\) dương, tính \(a\times b\bmod m\), với \(a,b\le m\le 10^{18}\).
Tương tự lũy thừa nhanh, ta biểu diễn một thừa số thành tổng các lũy thừa của 2. Vì nhân 2 và lấy modulo có thể chuyển thành cộng/trừ để tránh tràn, nên bài toán giải được trong \(O(\log m)\). Công thức đệ quy:
Tuy nhiên trong thực tế, cách này do phức tạp lớn hơn nên kém hiệu quả. Thường dùng nhân nhanh khi mô-đun trong long long.
Lũy thừa nhanh với số lớn
Kỹ năng trước: nhân số lớn
洛谷 P1045 [NOIP 2003 普及组] 麦森数
Cho \(P\) (\(1000 < P < 3100000\)), tính số chữ số của \(2^P−1\) và 500 chữ số cuối (thập phân), nếu thiếu thì thêm 0 ở đầu.
Cài đặt
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | |
Tiền xử lý lũy thừa nhanh khi cố định cơ số
Khi cơ số \(a\) cố định, có thể dùng phân khối để tiền xử lý và trả lời mỗi truy vấn trong \(O(1)\). Thuật toán này còn gọi là “lũy thừa ánh sáng”. Quy trình:
- Chọn \(s\), tiền xử lý \(a^0,a^1,\cdots,a^{s-1}\) và \(a^0,a^s,\cdots,a^{\lfloor p/s\rfloor s}\) vào hai mảng;
- Với truy vấn \(a^b\), tách \(b=\lfloor b/s\rfloor s+(b\bmod s)\), khi đó \(a^b=a^{\lfloor b/s\rfloor s}\cdot a^{b\bmod s}\), trả lời \(O(1)\).
Nếu \(b \in [0,n]\), thường chọn \(s\approx \sqrt{n}\) hoặc gần lũy thừa của \(2\). Chọn \(\sqrt{n}\) tối ưu tiền xử lý \(O(\sqrt{n})\), còn chọn lũy thừa của \(2\) giúp đơn giản hóa bằng bit.
Đặc biệt với lũy thừa modulo, cơ số \(a\) cố định ngầm nghĩa mô-đun \(m\) cũng cố định. Do định lý Euler mở rộng, biên trên mũ là \(n = 2\varphi(m)\); với mô-đun nguyên tố \(p\), biên là \(n = p - 1\). Cả hai trường hợp đều tiền xử lý \(O(\sqrt{m})\).
Mã tham khảo
1 2 3 4 5 6 7 8 9 10 11 12 | |
Bài tập
- UVa 1230 - MODEX
- UVa 374 - Big Mod
- UVa 11029 - Leading and Trailing
- Codeforces - Parking Lot
- SPOJ - The last digit
- SPOJ - Locker
- SPOJ - Just add it
Một phần nội dung trang này được dịch từ bài Бинарное возведение в степень và bản dịch tiếng Anh Binary Exponentiation. Bản tiếng Nga có giấy phép Public Domain + Leave a Link; bản tiếng Anh có giấy phép CC-BY-SA 4.0.
Last updated on this page:, Update history
Found an error? Want to help improve? Edit this page on GitHub!
Contributors to this page:OI-wiki
All content on this page is provided under the terms of the CC BY-SA 4.0 and SATA license, additional terms may apply