前言
在实际问题中,概率与数论算法常常能带来亚线性甚至常数级的性能突破。本篇只保留核心思路和最常用的 Java 代码示例,帮助你快速上手。
一、随机化算法
1. Rolling Hash(字符串快速匹配)
-
思路:把长度为 m 的字符串映射到一个整数(哈希值),并在滑动窗口时O(1) 更新:
// 计算初始哈希 long h=0, base=131, mod=1_000_000_007; for(int i=0;i<m;i++) h=(h*base + s.charAt(i))%mod; // 滚动更新:移出 s[i], 加入 s[i+m] long pow = modPow(base, m, mod); // base^m % mod h = ( (h*base - s.charAt(i)*pow%mod + mod) + s.charAt(i+m) )%mod;
-
应用:快速子串查找、文档去重、海量日志指纹。
2. Bloom Filter(布隆过滤器)
-
用位图+多哈希函数判断“是否可能存在”。
-
插入和查询均为 O(k),空间远小于存储所有元素。
BitSet bits=new BitSet(size);
int k=3; // 哈希函数个数
void add(String s){
for(int i=0;i<k;i++){
int pos=(hash(s,i)&0x7fffffff)%size;
bits.set(pos);
}
}
boolean contains(String s){
for(int i=0;i<k;i++){
int pos=(hash(s,i)&0x7fffffff)%size;
if(!bits.get(pos)) return false;
}
return true; // 可能存在
}
二、素数测试与因式分解
1. Miller–Rabin 素性测试
-
一种概率方法,O(k·log³n) 检测大数是否为素数。
-
实用时对不大发现一定基就足够准确。
boolean isPrime(long n){
if(n<2) return false;
long d=n-1, s=0;
while((d&1)==0){ d>>=1; s++; }
long[] bases={2,325,9375,28178,450775,9780504,1795265022};
for(long a:bases){
if(a%n==0) return true;
long x=modPow(a,d,n);
if(x==1||x==n-1) continue;
for(int r=1;r<s;r++){
x=modMul(x,x,n);
if(x==n-1) { x=-1; break; }
}
if(x!=n-1) return false;
}
return true;
}
2. Pollard’s Rho 因式分解
-
随机化寻找非平凡因子,期望 O(n¹ᐟ⁴)。
-
与 Miller-Rabin 配合使用,先检测再分解。
long pollardRho(long n){
if(n%2==0) return 2;
Random rnd=new Random();
long x=rnd.nextInt((int)n-2)+2, y=x, c=rnd.nextInt((int)n-1)+1, d=1;
while(d==1){
x=(modMul(x,x,n)+c)%n;
y=(modMul(y,y,n)+c)%n;
y=(modMul(y,y,n)+c)%n;
d=gcd(Math.abs(x-y), n);
if(d==n) return pollardRho(n);
}
return d;
}
三、快速幂与矩阵快速幂
1. 快速幂
-
计算 $a^n\bmod m$,O(log n)。
long modPow(long a,long e,long m){
long res=1;
while(e>0){
if((e&1)==1) res=res*a%m;
a=a*a%m;
e>>=1;
}
return res;
}
2. 矩阵快速幂
-
线性递推(如斐波那契)可转换矩阵幂,仍为O(log n)。
long fib(long n){
if(n<2) return n;
long[][] M={{1,1},{1,0}}, R={{1,0},{0,1}};
n--;
while(n>0){
if((n&1)==1) R=mul(R,M);
M=mul(M,M);
n>>=1;
}
return R[0][0];
}
四、中国剩余定理(CRT)与扩展欧几里得
1. 扩展欧几里得求逆元
long exgcd(long a,long b,long[] xy){
if(b==0){ xy[0]=1; xy[1]=0; return a;}
long d=exgcd(b,a%b,xy);
long x=xy[1], y=xy[0] - (a/b)*xy[1];
xy[0]=x; xy[1]=y;
return d;
}
long modInv(long a,long m){
long[] xy=new long[2];
exgcd(a,m,xy);
long inv=xy[0]%m;
return inv<0?inv+m:inv;
}
2. CRT
long crt(int[] a,int[] n){
int k=a.length;
long N=1; for(int v:n) N*=v;
long x=0;
for(int i=0;i<k;i++){
long Ni=N/n[i], inv=modInv(Ni,n[i]);
x=(x + a[i]*Ni%N*inv)%N;
}
return (x+N)%N;
}
五、数论变换(NTT)简介
-
NTT是FFT在模运算下的对应,解决模意义下的大规模多项式乘法,无浮点误差。
-
只需选合适 p=k·2^t+1 和本原根 $g$ 即可,复杂度 O(n\log n)。
// 省略预处理细节,核心蝶形操作示例
for(int len=1;len<n;len<<=1){
long w=modPow(g,(p-1)/(len<<1),p);
for(int i=0;i<n;i+=len<<1){
long wn=1;
for(int j=0;j<len;j++){
long u=a[i+j], v=a[i+j+len]*wn%p;
a[i+j]=(u+v)%p; a[i+j+len]=(u-v+p)%p;
wn=wn*w%p;
}
}
}
本篇小结
-
滚动哈希与布隆过滤器实现高效字符串与集合查询。
-
Miller–Rabin + Pollard’s Rho 组合实现大数素性检测与因式分解。
-
快速幂与矩阵幂将线性递推降至对数时间。
-
CRT与扩展欧几里得实现多模同余与模逆。
-
NTT是离散卷积在模域的快速实现。