一些小技巧/小trick
一些小技巧/小trick
待补
莫队值域分块
Binary gcd(Stein算法)
正常的欧几里得算法求gcd,时间复杂度由于取模、操作浪费等常数原因,往往较慢
而Binary gcd则以二进制为基础降低了常数
原理
若 \(a,b\) 均为偶数,则 \(\gcd(a,b)=2\gcd(\frac{a}{2},\frac{b}{2})\)
若 \(a,b\) 中有一个偶数(假设为 \(a\)),则 \(\gcd(a,b)=\gcd(\frac{a}{2},b)\)
若 \(a,b\) 均为奇数,则直接通过更相减损术得到 \(\gcd(a,b)=\gcd(\frac{a-b}{2},b)\)
再通过二进制位运算优化,可使复杂度变为常数较小的 \(O(\log n)\)
实现
直接模拟可得:
ll gcd(ll a, ll b) {
if(a == 0 || b == 0) return a+b;
if(a == b) return a;
if((a&1) == 0) {
if(b&1) return gcd(a>>1, b); // 一个偶数
else return gcd(a>>1, b>>1)<<1; // 两个偶数
}
if((b&1) == 0) return gcd(a, b>>1);
if(a > b) return gcd((a-b)>>1, b);
else return gcd((b-a)>>1, a);
}
循环形式的效率会更高
ll gcd(ll a, ll b) {
if(a == 0 || b == 0) return a+b;
if(a == b) return a;
ll i, j;
for(i = 0; (a&1) == 0; i++) a >>= 1;
for(j = 0; (b&1) == 0; j++) b >>= 1;
i = min(i, j);
while(1) {
if(a < b) swap(a, b);
a -= b;
if(a == 0) return b<<i;
while((a&1) == 0) a >>= 1;
}
return a;
}
而在 Luogu P5435 这道题中,我们这么写仍旧会T
这里引用一个函数 __builtin_ctz( ) ,它可以返回一个数二进制下末尾0的个数。优化后的代码:
int gcd(int a, int b) {
int i = __builtin_ctz(a), j = __builtin_ctz(b), t = min(i, j), d;
b >>= j;
while(a) {
a >>= i;
d = b-a;
i = __builtin_ctz(d);
if(a < b) b = a; // gcd(a-b, a) or gcd(b-a, b)
if(d < 0) a = -d; // 实测:abs较慢
else a = d;
}
return b<<t;
}
阈值分治/根号分治
调和级数
结论:\(\large{\frac{1}{1}+\frac{1}{2}+\frac{1}{3}+\cdots+\frac{1}{n}=\sum_{i=1}^n\frac{1}{i}=O(\log n)}\)
可用于计算复杂度等,如:
for(int i = 1; i <= n; i++) {
for(int j = i; j <= n; j += i) {
...
}
}
该段代码的复杂度即为 \(O(n\log n)\)

浙公网安备 33010602011771号