光速幂学习笔记
光速幂
黑科技……
使用情况
快速求 \(a^b \bmod m\) 的值。
在幂运算的底数和取余的模数已经确定的情况下可以使用光速幂。
比如 P3747 [六省联考 2017] 相逢是问候 中需要用光速幂,否则很难卡过。
\(\color{White}{\text{其实是因为那题才来学光速幂的}}\)
原理
根据拓展欧拉定理:
对于 \(a,m\in \mathbb{Z}\),有 \(a^b \equiv \begin{cases}a^b&b< \varphi(m) \\a^{\left( b \bmod \varphi(m) \right)+\varphi(m)}&b>\varphi(m)\end{cases} \pmod{p}\)
可以先把 \(b\) 的值缩小到 \(2 \times \varphi(m)\)。
令 \(k=\left\lceil\sqrt{b}\,\right\rceil\),对于 \(b\) 显然有 \(b = \left\lfloor\dfrac{b}{k}\right\rfloor \times k +b \bmod k\)
就可以将 \(a^b\) 转化为: $$a^b =a^{\left\lfloor\dfrac{b}{k}\right\rfloor \times k +b\bmod k}$$
写法
所以就能 \(O(\sqrt{n})\) 地预处理:
定义两个数组 \(x,y\)
令 \(x_i=a^i,i \in \left[ 1,k \right]\),显然可以通过 \(x_i=x_{i-1} \times a\) 得到数组 \(x\),其中要初始化数组为 \(x_0=1,x_1=a\)。
令 \(y_i=\left( a^k \right)^i\),显然可以通过 \(y_i=y_{i-1}\times a^k \text{也就是} x_k\) 得到数组 \(y\),其中初始化数组为 \(y_0=1,y_1=x_k\)。
所以 \(a^b \bmod p = x_{\left\lfloor\dfrac{b}{k}\right\rfloor} \times y_{b \bmod k}\)。
代码
Loj#162 快速幂 2
没用 \(\varphi\)
#include <bits/stdc++.h>
using namespace std;
#define L(i,j,k) for(int (i)=(j);i<=(k);(i)++)
const int p=998244352,N=4e4;
int n,x,a[N]={1},b[N]={1},bl;
signed main(){
ios::sync_with_stdio(0);cin.tie(0);
cin>>x>>n;bl=sqrt(x)+1;
L(i,1,bl) a[i]=1ll*a[i-1]*x%p;
L(i,1,bl) b[i]=1ll*b[i-1]*a[bl]%p;
L(i,1,n) cin>>x,cout<<1ll*a[x%bl]*b[x/bl]%p<<' ';
}
UPD:2022.10.6
终于回来更新使用 \(\varphi\) 的做法了。
国庆期间一道多校模拟。
Dahsa. 常数之神 小W
出题人声称光速幂要卡常才能过,且其复杂度与幂数的值域挂钩。
你这光速幂太假了
结果只要用拓展欧拉定理先降幂再进行预处理,就能吊打标算。
同时,标算的快速幂做法依赖于数据随机。
在数据随机的情况下,理论上只需进行 $\ln n $ 次快速幂,所以 \(O(n+ \ln n \log_2 V)\) 的时间复杂度可以通过。
但只要把数据改成反向递增的过程,就能轻松卡掉这一做法。
如果使用上面提到的使用 \(\varphi\) 先降幂后预处理的光速幂做法,可以将时间复杂度优化到 \(O(\sqrt{\text{模数}})\) 的预处理和 \(O(2\cdot n)\) 的询问的程度,能在不用任何包括 O2 在内的优化跑赢魔鬼卡常的标算。
这题中,由于模数已知,所以可以直接提前计算出其 \(\varphi\) 值和对应光速幂数组的预处理长度,算是一个常数优化。
而且,这种做法不依赖数据随机,时间复杂度能做到严格 \(O(n)\),只有 \(2\) 的询问常数,也不用像题解的光速幂那样分成三块,虽然预处理的时间复杂度降低了,但那样只会让询问的时间增长,但实际上光速幂的预处理常数可以忽略不计,所以此时只分成两块是更优的。
#include <bits/stdc++.h>
using namespace std;
#define L(i,j,k) for(int (i)=(j);i<=(k);(i)++)
#define R(i,j,k) for(int (i)=(j);i>=(k);(i)--)
#define FST ios::sync_with_stdio(0);cin.tie(0);
unsigned long long seed;
inline unsigned long long next_integer(){
seed^=seed<<19;seed^=seed>>31;
seed^=seed<<13;return seed;
}const int mod=2005232425;
int n,k;unsigned long long V,a[134217728],mi,lst;
long long x[90000]={1},y[90000]={1},bl=52440,ans,phi=1375016400;
void build(){
L(i,1,bl) x[i]=x[i-1]*k%mod;
L(i,1,bl) y[i]=y[i-1]*x[bl]%mod;
}void out(unsigned long long b){
if(b>=phi) b%=phi,b+=phi;
ans+=(lst=x[b%bl]*y[b/bl]%mod);
}int main(){
freopen("w.in","r",stdin);freopen("w.out","w",stdout);
FST cin>>n>>V>>k>>seed;
L(i,0,n-1) a[i]=next_integer()&V;build();
L(i,0,n-1) swap(a[i],a[next_integer()&n-1]);
mi=a[n-1];out(mi);
R(i,n-2,0)
if(a[i]<mi) out(mi=a[i]);
else ans+=lst;
cout<<ans;
}