题目链接
http://lightoj.com/volume_showproblem.php?problem=1306
https://cn.vjudge.net/problem/LightOJ-1306
题目大意
有形如
Ax+By+C=0
A
x
+
B
y
+
C
=
0
的方程,给定
A
A
、、
C
C
,求、
y
y
在到
x2
x
2
,
y1
y
1
到
y2
y
2
间的解数。
(所有数都为整数,数据范围
−108
−
10
8
到
108
10
8
,最多
10000
10000
组数据)。
解题思路
首先,我们抛开 x x 、的范围限制,仅仅是求解,那么我们可以用扩展欧几里得算法来解决。下面详细介绍如何求出一组解。
拓展欧几里得求出一组特解
无解情况
若 gcd(A,B)∤C g c d ( A , B ) ∤ C ,那么无解。
证明
设在此情况下问题有解,解为
x
x
,。
设
gcd(A,B)=t
g
c
d
(
A
,
B
)
=
t
,那么
t|A
t
|
A
,
t|B
t
|
B
,所以
t|Ax
t
|
A
x
,
t|By
t
|
B
y
,所以
t|(Ax+By)
t
|
(
A
x
+
B
y
)
,推出
t|C
t
|
C
,而
gcd(A,B)∤C
g
c
d
(
A
,
B
)
∤
C
,矛盾
命题得证
有解情况下求出一组解
我们将
A
A
、、
C
C
都除以。由等式的性质可得,这样做不影响答案。现在
A
A
、互质了。
我们不妨考虑
Ax+By=1
A
x
+
B
y
=
1
的情况。
因为
gcd(A,B)=1
g
c
d
(
A
,
B
)
=
1
,所以
gcd(B,AmodB)=1
g
c
d
(
B
,
A
mod
B
)
=
1
。(将
A
A
分为和余数部分,用反证法易得。)
所以
Bx′+(AmodB)y′=1
B
x
′
+
(
A
mod
B
)
y
′
=
1
同样有解。我们观察这两个式子的联系。
就有
那么我们不断迭代这一操作,直到 B=0 B = 0 ,那么此时 A=1 A = 1 (可由互质推出)。所以我们可取 x=1 x = 1 , y=0 y = 0 ,再退回去,就得到了原式的一组解。
由一组特解求解数
我们记特解为
x0
x
0
,
y0
y
0
。那么通解就是
由于 gcd(A,B)=1 g c d ( A , B ) = 1 ,所以
那么我们要求的就是符合条件的 t t 的个数。不如将, y y <script type="math/tex" id="MathJax-Element-55">y</script>分开考虑,那么就是求两个可行区间的交。这一部分的细节极多,严格操作很难直接说清楚,大家不如直接看程序,里面有详细的注释和说明。
参考程序
#include <cstdio>
#define LL long long
using namespace std;
LL A, B, C, x1, x2, y1, y2;
LL xl, xr, yl, yr;
void swap(LL & x, LL & y) {//交换两个数
LL t = x; x = y; y = t;
return;
}
LL gcd(LL x, LL y) {//求最小公约数
LL m = x % y;
while(m) { x = y; y = m; m = x % y; }
return y;
}
void exgcd(LL A, LL B, LL & x, LL & y) {//求不定方程一组特解(Ax+By=1)
if(B == 0) {
if(A == 1) x = 1; else x = -1;
y = 0; return;
}
exgcd(B, A % B, y, x);
y -= A / B * x;
return;
}
LL xxl, xxr, yyl, yyr;
void work2() {//特判0的情况
if(A == 0 && B == 0) {
if(C != 0) printf("0\n"); else printf("%lld\n", (x2 - x1 + 1) * (y2 - y1 + 1));
return;
}
if(B == 0) {
if(C % A != 0) {
printf("0\n");
return;
}
LL t = C / A;
if(x1 <= t && t <= x2) printf("%lld\n", y2 - y1 + 1); else printf("0\n");
return;
}
if(A == 0) {
if(C % B != 0) {
printf("0\n");
return;
}
LL t = C / B;
if(y1 <= t && t <= y2) printf("%lld\n", x2 - x1 + 1); else printf("0\n");
return;
}
}
void work() {
scanf("%lld%lld%lld%lld%lld%lld%lld", &A, &B, &C, &x1, &x2, &y1, &y2);
C = -C;
if(x1 > x2) swap(x1, x2);
if(y1 > y2) swap(y1, y2);
if(B == 0 || A == 0) {//特判0的情况
work2();
return;
}
LL t = gcd(A, B);
if(t < 0) t = -t;
if(C % t != 0) {
printf("0\n");
return;
}
A /= t; B /= t; C /= t;//使A,B,C互质
LL x0, y0;
exgcd(A, B, x0, y0);
x0 *= C; y0 *= C;//求出一组特解x0,y0
xxl = x1 - x0; xxr = x2 - x0;//求出x1,x2相对与x0的距离
yyl = y1 - y0; yyr = y2 - y0;//同上
xl = xxl / B; xr = xxr / B;//求出满足x关系式的大致的t的范围(左右边界可能有1的偏差)
yl = yyl / (-A); yr = yyr / (-A);//同上
if(xl * B < xxl) xl += (B > 0) ? 1 : -1;//调整左边界
if(xr * B > xxr) xr -= (B > 0) ? 1 : -1;//调整右边界
if(yl * (-A) < yyl) yl += ((-A) > 0) ? 1 : -1;//同上
if(yr * (-A) > yyr) yr -= ((-A) > 0) ? 1 : -1;
if(x0 + B * xl > x2 || x0 + B * xr < x1) { printf("0\n"); return; } //若不存在这样的t使得x处于x1,x2间
if(y0 - A * yl > y2 || y0 - A * yr < y1) { printf("0\n"); return; } //同上
if(xl > xr) swap(xl, xr);//再次判断原因说明:
// 由于我们除的系数有负,所以我们在这儿的"左",并不代表坐标较小的那个。而是有关于B,-A的正负性。
// 这个在前面处理的时候千万别搞错,后面为了方便判断交集,所以规定正常意义下的大小
if(yl > yr) swap(yl, yr);
// 分类讨论,确定交集
if(xl < yl) {
if(xr < yl) { printf("0\n"); return; }
if(yl <= xr && xr < yr) { printf("%lld\n", xr - yl + 1); return; }
if(yr <= xr) { printf("%lld\n", yr - yl + 1); return; }
}
if(yl <= xl && xl <= yr) {
if(xr < yr) { printf("%lld\n", xr - xl + 1); return; }
if(yr <= xr) { printf("%lld\n", yr - xl + 1); return; }
}
if(xl > yr) { printf("0\n"); return; }
}
int main() {
LL t;
scanf("%lld", &t);
for(LL i = 1; i <= t; i++) printf("Case %lld: ", i), work();
return 0;
}