DeMen Blog #3: Lũy thừa nhanh và hơn thế nữa!
Tác giả: Võ Khắc Triệu (DeMen100ns)
Kiến thức cần biết
- Lũy thừa nhanh (Bài viết sẽ nhắc lại cách tính lũy thừa nhanh)
- Quy hoạch động
Đặt vấn đề
Xét bài toán sau:
Bài toán
Tính:
Thuật toán ngây thơ
Đơn giản, ta sẽ vét cạn, khá dễ dàng nhưng cực kỳ chậm.
Độ phức tạp: .
Code:
int ans = 1;for(int i = 1; i <= b; ++i){ ans = (ans * 1ll * a) % m; //nhớ chuyển thành long long để không bị tràn số.}cout << ans;Thuật toán lũy thừa nhanh 1 (Dùng chia để trị)
Nhận xét:
- Với chẵn:
- Với lẻ:
Như vậy ta chỉ cần tính bằng , và sẽ giảm một nửa liên tục cho đến khi còn vì . Dễ thấy chỉ giảm lần.
Độ phức tạp:
Code:
int f(int a, int b, int m){ if (b == 0) return 1; if (b == 1) return a;
int f2 = f(a, b / 2, m); int ans = (f2 * 1ll * f2) % m; if (b % 2 == 1){ ans = (ans * 1ll * a) % m; }
return ans;}
int ans = f(a, b, m);cout << ans;Thuật toán lũy thừa nhanh 2 (Dùng biểu diễn nhị phân)
Nhận xét: Ta có thể tính trong , vì: .
Ta cũng biết rằng, mỗi số tự nhiên có thể được phân tách dưới dạng: , với . Ta dễ dàng xác định các biến thông qua biểu diễn nhị phân của .
Như vậy, ta có thể tính nhanh bằng cách tách thành các theo biểu diễn nhị phân như trên rồi nhân các vào với nhau. Các đã được tính ở bước trong .
Ví dụ: Tính với . Xét biểu diễn nhị phân của . . .
Độ phức tạp:
int ans = 1, pw = a;for(int i = 0; i < 30; ++i){ if (b >> i & 1){ //bit i cua b bat ans = (ans * 1ll * pw) % m; } pw = (pw * 1ll * pw) % m;}cout << ans;Mở rộng
Thực tế là, với mọi hàm thỏa:
- , với là một phép toán tử bất kỳ nào đó.
Thì bạn luôn tính được trong , với là độ phức tạp của phép toán tử .
Cụ thể, tương tự với lũy thừa nhanh, bạn có thể tính bằng cách tính trước , rồi tính bằng cách biểu diễn thành dạng nhị phân rồi gộp vào.
Thực tế, nếu ta coi thì bài toán sẽ trở thành bài toán tính lũy thừa nhanh.
Có một số blog gọi đây là x2 +1 trick, thường dùng để tối ưu các hàm quy hoạch động.
Áp dụng
Bài: Olympic Sinh Viên 2022 - Chuyên tin - Khôi phục dữ liệu
Tóm tắt: Đếm số cách chọn ba xâu nhị phân độ dài thỏa:
- Có ít nhất một xâu có bit được bật.
- Tổng số bit bật của ba xâu chia hết cho .
- Không tồn tại vị trí nào bật bit trên cả ba xâu.
In ra đáp án
Limit: .
Lời giải quy hoạch động:
Gọi là số cách chọn ba xâu nhị phân thỏa điều kiện và có tổng bit bật là . Ta dễ suy ra công thức truy hồi sau:
Đáp án sẽ là (vì ta cần loại trường hợp không có xâu nào có bit bật).
Cách này sẽ có độ phức tạp là , đủ ăn subtask 2, nhưng chưa đủ nhanh để qua subtask cuối.
Nhận xét:
- Dễ tính được .
- , đúng với mọi .
Vậy nếu ta coi và toán tử là với mọi cần tính thì ta có thể làm bài này trong , với là độ phức tạp của toán tử.
Code:
const int MOD = 1e9 + 7;inline void add(int &x, int y, int mod = MOD) { x += y; while (x >= mod) x -= mod; while (x < 0) x += mod;}inline void mul(int &x, int y, int mod = MOD) { x = (x * 1LL * y) % mod;}inline int prod(int x, int y, int mod = MOD) { return mul(x, y, mod), x;}inline int sum(int x, int y, int mod = MOD) { return add(x, y, mod), x;}inline int bpow(int x, int y, int mod = MOD) { int ans = 1; while (y) { if (y & 1) mul(ans, x, mod); mul(x, x, mod); y >>= 1;} return ans;}inline int Inv(int x, int mod = MOD) { return bpow(x, mod - 2, mod);}inline int Div(int x, int y, int mod = MOD) { return prod(x, Inv(y, mod), mod);}
const int N = 2e5 + 5;const long long INF = 1e18 + 7;const int MAXA = 1e9;const int B = sqrt(N) + 5;
int n, k;int pw[32][101], dp[32][101];
void solve(){ int n, k; cin >> n >> k;
pw[0][0]++; pw[0][1 % k] += 3; pw[0][2 % k] += 3; for(int i = 1; i < 32; ++i){ for(int s1 = 0; s1 < k; ++s1){ for(int s2 = 0; s2 < k; ++s2){ add(pw[i][(s1 + s2) % k], prod(pw[i - 1][s1], pw[i - 1][s2])); //pw[i] = f(2^i) = dp[2 ^ i][] } } } dp[0][0] = 1; for(int i = 0; i < 30; ++i){ if (!(n >> i & 1)) { for(int s = 0; s < k; ++s) dp[i + 1][s] = dp[i][s]; continue; }
for(int s1 = 0; s1 < k; ++s1){ for(int s2 = 0; s2 < k; ++s2){ add(dp[i + 1][(s1 + s2) % k], prod(dp[i][s1], pw[i][s2])); } } } cout << sum(dp[30][0], -1);}Ngoài ra bài còn có một cách dùng nhân ma trận trong (về bản chất thì nhân ma trận cũng dùng lũy thừa nhanh để tính ).
