光速幂学习笔记

光速幂

黑科技……

使用情况

快速求 abmodm 的值。
幂运算的底数和取余的模数已经确定的情况下可以使用光速幂。
比如 P3747 [六省联考 2017] 相逢是问候 中需要用光速幂,否则很难卡过。
其实是因为那题才来学光速幂的

原理

根据拓展欧拉定理:

对于 a,mZ,有 ab{abb<φ(m)a(bmodφ(m))+φ(m)b>φ(m)(modp)

可以先把 b 的值缩小到 2×φ(m)
k=b,对于 b 显然有 b=bk×k+bmodk
就可以将 ab 转化为: ab=abk×k+bmodk

写法

所以就能 O(n) 地预处理:
定义两个数组 x,y
xi=ai,i[1,k],显然可以通过 xi=xi1×a 得到数组 x,其中要初始化数组为 x0=1,x1=a
yi=(ak)i,显然可以通过 yi=yi1×ak也就是xk 得到数组 y,其中初始化数组为 y0=1,y1=xk
所以 abmodp=xbk×ybmodk

代码

Loj#162 快速幂 2
没用 φ

#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

终于回来更新使用 φ 的做法了。
国庆期间一道多校模拟。
Dahsa. 常数之神 小W
出题人声称光速幂要卡常才能过,且其复杂度与幂数的值域挂钩。
你这光速幂太假了
结果只要用拓展欧拉定理先降幂再进行预处理,就能吊打标算。

同时,标算的快速幂做法依赖于数据随机。
在数据随机的情况下,理论上只需进行 lnn 次快速幂,所以 O(n+lnnlog2V) 的时间复杂度可以通过。
但只要把数据改成反向递增的过程,就能轻松卡掉这一做法。

如果使用上面提到的使用 φ 先降幂后预处理的光速幂做法,可以将时间复杂度优化到 O(模数) 的预处理和 O(2n) 的询问的程度,能在不用任何包括 O2 在内的优化跑赢魔鬼卡常的标算。

这题中,由于模数已知,所以可以直接提前计算出其 φ 值和对应光速幂数组的预处理长度,算是一个常数优化。

而且,这种做法不依赖数据随机,时间复杂度能做到严格 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;
}
posted @   AIskeleton  阅读(598)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示