题解 P4549 【【模板】裴蜀定理】

JustinRochester

2019-02-14 13:40:33

Solution

这里给出非递归的 gcd 做法 --- **【分析】** -- 要看证明内容的就麻烦翻一下别人的题解了,这里本蒟蒻就不班门弄斧了,直接讲一下解题思路: 显然,就是对于读入的 $n$ 个数,输出他们的最大公因数 假设 $gcd(a,b)=g,gcd(a,b,c)=d$ 由 $d|a$ 且 $d|b$ 得 $d|gcd(a,b)$ 即 $d|g$ 又 $\because d|c$ $\therefore d|gcd[\quad gcd(a,b),c\quad]$ 因此,我们只要对每次读入的一个数,和上几次得出的最大公因数用一次 gcd 即可 当然,因为答案要求正数,所以读入的负数直接转化为正数即可 ---- 接下来开始讲非递归 gcd 的原理,不求甚解的同学或者一看就懂的 dalao 麻烦往下跳 常规的 gcd ,是用递归实现的: ```cpp int gcd(int a,int b){ if(b==0) return a; else return gcd(b,a%b); } ``` 当然,在压行选手眼中,它是这样的: ```cpp int gcd(int a,int b) { return (!b)?a:gcd(b,a%b); } ``` 今天呢,我这边给出非递归打发。这是我在一篇文章中看到的黑科技: 我们想,$gcd(a,b)=gcd(b,a\%b)=...=gcd(r,0)$ 所以 $r$ 为最大公因数。 那么,实际上就是说,递归边界是 $b==0$ 这个应该很显然吧 而当 C++ 中的 while() 循环,内部是一个赋值表达式的时候 一旦赋值过后的该值为 $0$ ,就会退出循环 这和我们的递归边界是很像的。 而对于 $(a,b)$ 变成 $(b,a\%b)$ ,可以理解为: 1. $(a,b)$ 变为 $(a\%b,b)$ 2. $(a\%b,b)$ 交换两个值,变为 $(b,a\%b)$ 这两步的合成 而对于交换两个不同的数 $a,b$ ,有一个黑科技打法: ```cpp a^=b^=a^=b; ``` 程序从右往左执行: 1. a^=b,a 变成 a^b 2. b^=a,b 变成 b^(a^b)=b^b^a=a 3. a^=b,a 变成 (a^b)^a=a^a^b=b 至此,足以完成交换操作 --- 实际上,我们把上述步骤结合一下,就可以实现非递归了,这边就直接给出代码了,我觉得应该可以理解了: ```cpp while(b^=a^=b^=a%=b); ``` 最后, $a$ 就是最大公约数 当然,还有一个小细节,大家可以想一想,为什么? ```cpp int &me=LRJ; me->output("想一想,为什么"); ``` 刚刚那个代码的前面应该要加上一个判定,变成: ```cpp if(b) while(b^=a^=b^=a%=b); ``` --- **【代码】** -- 那本蒟蒻就放 ~~我那码风极丑的~~ 代码了 ```cpp #include<cstdio> using namespace std; inline int read(){ register int ans=0;register char c=getchar(); while(c<48||c>57) c=getchar(); while(c>=48&&c<=57) ans=(ans<<3)+(ans<<1)+(c^48),c=getchar(); return ans; }//读入优化,并且直接不读负号 int main(){ register int n=read()-1,s=read(); while(n--){ if(s==1) break; register int tmp=read(); if(tmp) while(tmp^=s^=tmp^=s%=tmp); } printf("%d",s); return 0; } ```