UVA11768 Lattice Point or Not

21 May 2019 | duanyll | Tags: OI 题解 数论 exgcd

洛谷水黑题,不过考试的时候把我坑惨了,推了一个半小时都没有推出来,太弱了。。。

题意

在二维坐标系中给定两个点$A(x_1, y_1)$和$B(x_2, y_2)$,$x_1$, $x_2$, $y_1$, $y_2$均为$0.1$的整数倍,求线段$AB$经过多少个格点。

分析

给出的是两点坐标求直线,应该用直线的两点式方程入手。

\[\frac{y-y_1}{x-x_1}=\frac{y-y_2}{x-x_2}\]

交叉相乘整理得一般式

\[(y_2-y_1)x + (x_1-x_2)y = y_2x_1 - y_1x_2\]

求这样的式子的整数解,显然需要exgcd。但是题目给出的是小数,还好只有一位,坐标都扩大十倍。

\[(10y_2-10y_1)\cdot \color{red}{10}x + (10x_1-10x_2)\cdot\color{red}{10}y = 100y_2x_1 - 100y_1x_2\]

写代码时不要忘了乘上红色的系数!

然后exgcd常规操作,求出直线上的一个整点$(x_0,y_0)$

\[x_0 = x*\frac{c}{gcd(a,b)},y_0=y*\frac{c}{gcd(a,b)}\]

并且其它整点满足

\[x = x_0 + \frac{b}{gcd(a,b)} * t,y = y_0 - \frac{a}{gcd(a,b)} * t\]

这样求出来的点保证在直线上,我们就不用考虑y坐标,只用管x坐标了。

x坐标上整点出现的周期是$abs(b/gcd(a,b))$,将$x_0$移动到目标区间开头:

int64 xbegin = x + (l - x) / xrep * xrep;

答案就是

(r - xbegin) / xrep + 1

代码

注意特判两点式不成立的情况:平行于坐标轴。

#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <cmath>
#include <fstream>
#include <cctype>
using namespace std;

typedef long long int64;
const int64 INF = 0x3f3f3f3f3f3f3f3fll;

int64 exgcd(int64 a, int64 b, int64 &x, int64 &y) {
    if (!b) {
        x = 1;
        y = 0;
        return a;
    }
    int64 GCD = exgcd(b, a % b, x, y);
    int64 tmp = x;
    x = y;
    y = tmp - a / b * y;
    return GCD;
}

inline double dbread() {
    double X = 0, Y = 1.0;
    int w = 0;
    char ch = 0;
    while (!isdigit(ch)) {
        w |= ch == '-';
        ch = getchar();
    }
    while (isdigit(ch)) X = X * 10 + (ch ^ 48), ch = getchar();
    ch = getchar(); 
    while (isdigit(ch)) X += (Y /= 10) * (ch ^ 48), ch = getchar();
    return w ? -X : X;
}

int main() {
	int tcnt;
	cin >> tcnt;
	for (int T = 1; T <= tcnt; T++) {
		double x1 = dbread();
		double y1 = dbread();
		double x2 = dbread();
		double y2 = dbread();
		
		int64 X1 = x1 * 10, X2 = x2 * 10, Y1 = y1 * 10, Y2 = y2 * 10;
		if (x1 == x2) {
			if (X1 % 10 != 0) {
				cout << 0 << endl;
			} else {
				if (y2 < y1) {
					swap(y1, y2);
				}
				cout << floor(y2) - ceil(y1) + 1 << endl;
			}
		} else if (y1 == y2) {
			if (Y1 % 10 != 0) {
				cout << 0 << endl;
			} else {
				if (x2 < x1) {
					swap(x2, x1);
				}
				cout << floor(x2) - ceil(x1) + 1 << endl;
			}
		} else {
			int64 A = (Y2 - Y1) * 10, B = (X1 - X2) * 10;
			int64 C = Y2 * X1 - Y1 * X2;
			int64 x, y;
			int64 GCD = exgcd(A, B, x, y);
			if (C % GCD != 0) {
				cout << 0 << endl;
			} else {
				x = x * C / GCD;
				int64 xrep = abs(B / GCD);
				if (x1 > x2) {
					swap(x1, x2);
				}
				int64 l = ceil(x1);
				int64 r = floor(x2);
				if (l <= r) {
					int64 xbegin = x + (l - x) / xrep * xrep;
					if (xbegin < l) {
						xbegin += xrep;
					} 
					if (xbegin > r) {
						cout << 0 << endl;
					} else {
						cout << (r - xbegin) / xrep + 1 << endl;
					}
				} else {
					cout << 0 << endl;
				}
			}
		}
	}
}