【浅谈】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); 
	}
}
posted @ 2021-08-19 18:12  仰望星空的蚂蚁  阅读(17)  评论(0)    收藏  举报  来源