二进制gcd(Binary gcd)
二进制gcd(Binary gcd)
前置知识:(最大公约数),位运算
介绍
众所周知利用欧几里得算法(辗转相除法)可以做到在 的时间复杂度内求出两个数的最大公约数
但是有时候这还是不够快,例如这道题——Luogu P5435
如果直接辗转相除硬求 就会 T 得飞起,但是题解里那种方法 预处理 查询又好麻烦,那么有没有一种更快的直接求两个数 的方法呢?
二进制 就是这样一种算法,它利用计算机优秀的二进制处理能力来计算
原理
假设现在我们需要求 满足
我们将 分为 种情况讨论
第一种,。显然
第二种, 或 。根据最大公约数的定义当 时 ,当 时
第三种, 均为偶数。对于这种情况,显然 是公约数之一,直接将两个数都除以 ,再递归下去求 即可,
第四种, 中有且仅有一个偶数。不妨设 是偶数,那么显然 不是公约数之一,直接将 除以 求 即可,
第五种, 均为奇数。不妨设 ,那么有 ,为什么呢?下面来证明一下
设 且 ,先考虑证明 (即“更相减损术”)
,那么有 ,即证
考虑反证法,假设 其中 ,再设
就有 ,那么 与 互质矛盾,得证
综上
由于 均为奇数, 就是偶数,那么问题就变成了和情况四一样, 显然不是公约数之一,故
实现
直接根据上图模拟,我们可以得到以下代码(注意:~
的运算优先级高于 |
和 &
)
int gcd(int a,int b)
{
if(a==b) return a;//a=b
if(!a) return b;//a=0
if(!b) return a;//b=0
if(~a&1)//a是偶数
{
if(b&1)//b是奇数
return gcd(a>>1,b);
else//b是偶数
return gcd(a>>1,b>>1)<<1;
}
if(~b&1)//a是奇数b是偶数
return gcd(a,b>>1);
//均为奇数
if(a>b) return gcd((a-b)>>1,b);
return gcd((b-a)>>1,a);
}
这样使用了大量的位运算,虽然看起来比辗转相除法递归层数更多,但是实际上大大优化了运算速度
当然我们可以把递归展开写成循环的形式,效率还能更高
首先把两个数公约数中的所有 提出来,也就是把二进制末尾的 全部提出来,然后两个数就至少有一个是奇数,把两个数末尾的 再分别去掉就能保证一定是两个奇数,利用更相减损术循环求解就行,具体看代码
int gcd(int a,int b)
{
if(!a)return b;//a=0
if(!b)return a;//b=0
int shift;//a,b二进制下末尾0的个数
for(shift=0;~(a|b)&1;++shift)a>>=1,b>>=1;//把两个数末尾的shift个0都去掉
while(~a&1)a>>=1;//现在两个数至少有一个奇数,2一定不是公约数
//现在a一定是奇数
do
{
while(!(b&1))b>>=1;
//现在两个数一定都是奇数
if(a>b)swap(a,b);
b-=a;//更相减损
}while(b);
return a<<shift;//把最开始去掉的2^shift的公约数加上
}
注意到每次还是都使用 while
来去除二进制下末尾的 ,效率低下,可以利用硬件函数 __builtin_ctz()
来继续加速
同时只有直接输入 时才可能 或是 ,所以如果输入保证不为 就可以去掉这个判断
再稍加卡常就得到了终极代码
inline int gcd(int a, int b)
{
register int az=__builtin_ctz(a),bz=__builtin_ctz(b),z=az>bz?bz:az,diff;
b>>=bz;
while(a)
{
a>>=az;
diff=b-a;
az=__builtin_ctz(diff);
if(a<b)b=a;
a=diff<0?-diff:diff;
}
return b<<z;
}
这样我们就成功地利用 求 切掉了 Luogu P5435 这道需要 预处理 求 的题目
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 一文读懂知识蒸馏
· 终于写完轮子一部分:tcp代理 了,记录一下