直线整点问题,例题(NEUQOJ改编):扩展欧几里得算法的应用。
二维空间之旅
时间限制(普通/Java):1000MS/2000MS空间限制:65536Kbyte
通过:23 总提交:127
描述
有一天jhjjhj不小心掉入了二维空间,在这个二维空间中,jhjjhj感觉很是惬意,他一会儿在直线上跳舞,一会儿又从很多个同心圆中穿过,一会又在一个大正方形里面和一群小线段玩捉迷藏,很是惬意。玩着玩着,jhjjhj忘记了时间,他突然想起,虽然二维空间也是这样美好,但是自己还是要返回三维空间的,但是自己现在为止还不知道如何返回自己的世界,于是向优美的心形女王求助。心形女王告诉他,返回三维空间的大门被一条直线(直线方程是ax+by=c)守卫,而返回三维空间的唯一方法就是点亮某一范围内直线所在整点的宝石。现在,jhjjhj想知道到底在这个范围内有多少个整点呢?焦急万分的jhjjhj已经开始抓耳挠腮,失去了思考能力了,所以通过时空电话向聪明的你进行求助,你能帮帮他吗?
输入
输入的第一行是一个整数T,表示测试案例的数量。接下来有2T行中,每组测试案例包括两行,第一行包括三个正整数a,b,c,第二行包括四个整数x1,x2,y1,y2(-100<=x1,x2,x3,x4<=100)。
输出
对于每组测试案例,输出一个整数,表示直线ax+by=c在x属于[x1,x2],y属于[y1,y2]上,有多少的整点在该直线上。
样例输入
2
3 6 3
-1 1 -1 1
3 5 6
-10 10 -10 10
样例输出
2
4
直线上的点:求直线ax+by=c上有多少个整点(x,y)满足x属于[x1,x2],y属于[y1,y2]。
算法:欧几里得算法。
算法功能:获得整数a,b的最大公约数。
代码:
intgcd(int a,int b) {return !b?a:gcd(b,a%b);}
算法:扩展欧几里得算法。
算法功能:找出一对儿整数解(x,y),使得ax+by=gcd(a,b),其中gcd(a,b)表示a,b的最大公约数。
代码:
voidextra_gcd(int a,int b,int &x,int &y)
{
if(!b) {x=1;y=0;}
else {extra_gcd(b,a%b,y,x);y-=x*(a/b);}
}
定理1:设a,b,c为任意整数,g=gcd(a,b),若方程ax+by=c的一组整数解为(x0,y0),则它的任意整数解可以表示为(x0+kb’,y0-ka’)。其中a’=a/g,b’=b/g,k取任意整数。
特别说明:以上定理假设gcd(a,b)不等于0。当gcd(a,b)=0时,a,b至少有一个为0,这种情况特殊讨论。
定理1的证明:
假设ax+by=g的一组解为(x1,y1)且已得出,而进一步假设我们知道另外一组解为(x2,y2),则有ax1+by1=g,ax2+by2=g。
则:ax1+by1=ax2+by2
得:a(x1-x2)=b(y2-y1),g=gcd(a,b)。
方程两边同时除以g,得a’(x1-x2)=b’(y2-y1)。
易得a’与b’互素,所以x1-x2必为b’的整数倍,这样才能保证等式成立。
假设x1-x2=kb’,则有x1=x2+kb’
而将x1-x2=kb’带入方程,a’kb’=b’(y2-y1),易得:y2-y1=ka’,即y1=y2-ka’
由于以上推论与等式右边的c值无关
故,定理1得证。
定理2:若方程ax+by=g的一组整数解为(x0,y0),那么方程ax+by=c的整数解为(x0*c/g,y0*c/g)。特别说明:当c/g不为整数时,ax+by=c无整数解。
定理2的证明:
若方程ax+by=g的一组整数解为(x0,y0),则有ax0+by0=g。
两边同时除以g,所以有a/gx0+b/gy0=1,即(x0,y0)是a’x+b’y=1的整数解。
两边同时乘以c,得a(c*x0/g)+b(c*y0/g)=c,即(x0*c/g,y0*c/g)是方程ax+by=c的整数解
故,定理2得证。
对于直线上正点数量问题的解题步骤:
(1)根据欧几里得算法算出g=gcd(a,b)。进而计算出a’=a/g,b’=b/g。
(2)由扩展欧几里得算法算出满足ax+by=g的一对儿整数解(x0,y0),由定理2知,满足ax+by=c的一对儿整数解为(x0*c/g,y0*c/g)。当c/g不为整数时,该问题无解。
(3)由定理1可知ax+by=c的任意整数解可以写作(x0*c/g+k*b/g,y0*c/g-k*a/g)。
由x0*c/g+k*b/g属于[x1,x2],可以解得k的一个范围(前后适当取整),由y0*c/g-k*a/g属于[y1,y2]可以解得另一个k的范围,两个范围取交集,得k属于[k1,k2],故整点个数为k2-k1+1个。
程序代码如下(C++)
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
int gcd(int x,int y) {return!y?x:gcd(y,x%y);} //欧几里得算法
void gcd(int a,int b,int &x,int &y) //扩展欧几里得算法
{
if(!b){x=1;y=0;}
else {gcd(b,a%b,y,x);y-=x*(a/b);}
}
int max(int x,int y) {return x>y?x:y;} //最大值算法
int min(int x,int y) {return x<y?x:y;} //最小值算法
int main()
{
intT,a,b,c,x1,x2,y1,y2;
intaa,bb,g,x0,y0;
intlx,rx,ly,ry;
while(~scanf("%d",&T))
{
while(T--)
{
scanf("%d%d%d",&a,&b,&c);
scanf("%d%d%d%d",&x1,&x2,&y1,&y2);
g=gcd(a,b);
aa=a/g;
bb=b/g;
gcd(a,b,x0,y0);
if(c%g==0)
{
x0*=c/g;
y0*=c/g;
lx=(x1-x0)/bb;
rx=(x2-x0)*1.0/bb-0.99999999; //对于下界,向上取整
ly=(y0-y2)/aa;
ry=(y0-y1)*1.0/aa-0.99999999; //对于下界,向上取整
if(rx<ly||ry<lx) printf("0\n");
else
{
lx=max(lx,ly);
rx=min(rx,ry);
printf("%d\n",rx-lx+1);
}
}
else printf("0\n");
}
}
return 0;
}