线段上格点个数---挑战编程---欧几里得
线段上格点的个数
何为格点?
横纵坐标均为整数的点,可以将坐标面看做一个二维表,线段即两个点为顶点的矩形的对角线,线段由格点组成
问题:
给定平面上的两个格点P1(x1,y1)和P2(x2,y2),线段上P1P2上,除P1和P2以外一共有多少格点
方法1:
虽然可以用穷举法,遍历min(x1,x2)≤x≤max(x1,x2)且min(y1,y2)≤y≤max(y1,y2)的格点可以得到正确答案,但是复杂度确实O(|x1−x2|×|y1−y2|)
方法2:
其实这个题的答案是|x1−x2|和|y1−y2|的最大公约数减去1。(注意,|x1−x2|=0且|y1−y2|=0时,答案为0)
原因,首先看一下|x1−x2|和|y1−y2|的最大公约数代表的是啥? 其实可以看成 在横向和竖向的最大的公共等分数, 比如 6 的等分点可以是 1:1:1:1:1:1分成6份 ,也可以是 2:2:2分成3份,或者是 6,只有1份。(其实对应的是 6能被6,3,1整除) 那么 6和9的最大公共等分数是3,即6分为 2:2:2 , 9分为3:3:3. 那么边长为6和9的矩形,按照这样分会是什么情况呢?
通过上图可以看出,大矩形的对角线正好经过 (2,3),(4,6),(6,9) 除开(6,9),就是本体所要求的点。这就是为什么这个题的答案是|x1−x2|和|y1−y2|的最大公约数减去1。
数学:
那这个题可以转换为求最大公约数的问题,最大公约数一般使用辗转相除法
辗转相除法的原理:
如果有两个自然数a和b(a>b), 可以写成a=k×b+c
情况1:如果c=0,那么gcd(a,b)=b
情况2:如果c≠0, 那么gcd(a,b)=gcd(b,c)
下面提供了3个求最大公约数的方法(顺便一提,a和b的最小公倍数为a×bgcd(a,b)
#include <iostream> #include <cmath> using namespace std; ///非递归的方法 int gcd1(int a,int b) { int c; do { c=a%b; a=b; b=c; }while(c!=0); return a; } ///递归的方法 int gcd2(int a,int b) { if(b == 0){ return a; }else{ return gcd2(b,a%b); } } ///辗转相减法 int gcd3(int a,int b) { int c=a-b; if(c<0){ return gcd3(b,a); } while((a-b)!=0) { a=a-b; if(a<b){ int tmp=a; a=b; b=tmp; } } return a; } int main(void) { int x1,x2,y1,y2; cout<<"input x1,y1,x2,y2"<<endl; cin>>x1>>y1>>x2>>y2; int abs_x=abs(x1-x2); int abs_y=abs(y1-y2); int g=gcd3(abs_x,abs_y); cout<<g-1<<endl; int x_step=(x2-x1)/g; int y_step=(y2-y1)/g; for(int i=0;i<=g;i++)///0时起点 g时终点;这是一个由起点按一定跨度,一步步到达终点的过程 { cout<<"("<<x1+i*x_step<<","<<y1+i*y_step<<")"<<endl; } }