【浅谈】exgcd 中的整数溢出
首先我们知道, exgcd 中求得 x,y 的绝对值满足 : |x| <=b , |y| <= a 。同时 x, y 在数据大时远小于 a, b 规模。
例一:CF1244C The Football Season
首先看数据范围,a, b<=1e5, p<=1e17 , 来看错误的溢出写法:
x=(x*(p/r)%(b/r)+(b/r))%(b/r)
y=(c-a*x)/b
当 r=1 ,p=1e17 时, x*(p/r) 直接爆 longlong 了。
正确写法:
ll mo=b/r
x=(x%mo*((p/r) % mo)%mo + mo)%mo
y=(c-a*x)/b
这里用到的方法是提前对 p/r 取模。因为 a,b,mo,x <= 1e5 ,所以这里不会溢出。
例二:HDU_4180_RealPhobia
数据范围:a,b <= 2^31
ll mo=b/r;
x=(x%mo*(r%mo)%mo+mo)%mo;
y=(1ll-a*x)/b
前文我们提到 |x| 的规模远小于 a,b 规模,但是真的没有隐患吗?
input: 2147483647 2147483648
output: 2147483647 -2147483646
(取模后的结果)
input: 3147483647 3147483650
output: -1049161217 1049161216
(这是取模前的结果)
可以发现取模前和取模后的结果都不安全。本题中,1-a*x=1-ab>=1-a(a-1)>=-2^62 + 2^31 +1 ,刚好不会爆 longlong 。
摸索一下午得出的结论 : 写一个快速乘除法可以有效避免溢出。
A C AC AC C o d e Code Code
ll fmul(ll x,ll y,ll mod) {
x%=mod,y%=mod; if(y<0) y=-y,x=-x;
ll sum(0);
for(;y;y>>=1) {
if(y&1) sum=(sum+x)%mod;
x=(x+x)%mod;
}
return sum;
}
ll fdiv(ll x,ll y,ll mod) {
ll sum(0),tot(0),tmp(x/mod); x%=mod;
for(;y;y>>=1) {
if(y&1) sum+=tmp,tot+=x;
if(tot>=mod) sum++,tot-=mod;
x<<=1; if(x>=mod) tmp++,x-=mod;
}
return sum;
}
signed main() {
while(~scanf("%lld%lld",&a,&b)) {
if(gcd(a,b)>1) {printf("sorry\n");continue;}
ll x,y,r; exgcd(a,b,x,y,r);
if(x>=0) {printf("%lld %lld\n",x,y);continue;}
ll mo=b/r;
x=(fmul(x,r,mo)+mo)%mo;
y=fdiv(1,1,b)-fdiv(a,x,b);
//一般地,可以写成 y=fdiv(c,1,d)-fdiv(a,x,d)
printf("%lld %lld\n",x,y);
}
}

浙公网安备 33010602011771号