% 给定一个面积 n × m n\times m n×m 的区域,在其中放若干个边长为 l l l 的正方形矩阵,使得每个正方形矩阵的边缘与和其相邻的正方形矩阵的边缘(或相邻的整个大的区域的边缘)的距离都相等,求合法的方案数。
数据范围 1 ⩽ n , m , l ⩽ 1 0 13 1\leqslant n,m,l\leqslant 10^{13} 1⩽n,m,l⩽1013。
% 你拿到这道题,想了很久,发现只会考虑朴素算法,你尝试着枚举一行放 x x x 个正方形矩阵,一列放 y y y 个矩阵,其中 i × l ⩽ n , j × l ⩽ m i\times l\leqslant n,j\times l\leqslant m i×l⩽n,j×l⩽m 且 i , j i,j i,j 都是正整数,你发现,这对 ⟨ l , r ⟩ \langle l,r\rangle ⟨l,r⟩ 合法的充要条件是
n − x l x + 1 = m − y l y + 1 \frac{n-xl}{x+1}=\frac{m-yl}{y+1} x+1n−xl=y+1m−yl 你考虑到除法可能会丢失精度,你化简了一下,得到了一个优秀的式子和不优秀的暴力。
( n − x l ) ( y + 1 ) = ( m − y l ) ( x + 1 ) (1) (n-xl)(y+1)=(m-yl)(x+1)\tag{1} (n−xl)(y+1)=(m−yl)(x+1)(1)
for(long long x=1;x*l<=n;x++)
for(long long y=1;y*l<=m;y++)
if((n-x*l)*(y+1)==(m-y*l)*(x+1))
ans++;
% 你看着那行判断,沉思许久,你发现对于任意满足 ( 1 ) (1) (1) 式的 x x x,都有唯一成立的 y y y。你尝试把 ( 1 ) (1) (1) 式拆开
n y + n − x y l − x l = m x + m − x y l − y l ny+n-xyl-xl=mx+m-xyl-yl ny+n−xyl−xl=mx+m−xyl−yl
% 化简,整理,尝试让左边只剩下 y y y。
y = ( m + l ) x + m − n n + l y=\frac{(m+l)x+m-n}{n+l} y=n+l(m+l)x+m−n
% 于是,你得到了一个优秀少许的做法,枚举 x x x,判断 y y y 是不是正整数。
for(long long x=1;x*l<=n;x++)
if(((m+l)*x+m-n)%(n+l)==0) ++ans;
% 你继续化简,发现没有任何余地,或者说,你的脑子根本不给你机会想到比这更优秀的做法,你开始放弃这种想法,并在为几秒钟之前的自己白忙活了而感到幸灾乐祸。你继续考虑 ( 1 ) (1) (1) 式,你尝试将它变得更加优美一些。
( m + l ) x + ( − n − l ) y = n − m (m+l)x+(-n-l)y=n-m (m+l)x+(−n−l)y=n−m 你定眼一看,发现 n , m , l n,m,l n,m,l 都是已知的,你令 a = m + l , b = − n − l , c = n − m a=m+l,b=-n-l,c=n-m a=m+l,b=−n−l,c=n−m 问题变成了关于 x , y x,y x,y 的一元二次方程 a x + b y = c ax+by=c ax+by=c 的正整数解的个数,再 浪费时间 想想,你想到了扩展欧几里得算法,并用它求出了不定方程的特解 x 0 , y 0 x_0,y_0 x0,y0,还导出了所有解的通式
{ x = x 0 + k × b ( a , b ) y = y 0 − k × a ( a , b ) \begin{cases} x=x_0+k\times \dfrac b{(a,b)}\\ y=y_0-k\times \dfrac{a}{(a,b)} \end{cases} ⎩⎪⎨⎪⎧x=x0+k×(a,b)by=y0−k×(a,b)a
% 你发现你要求的,便是使得 x , y x,y x,y 均为正整数的整数 k k k 的个数。将这个限制代入,得
{ 1 ⩽ x 0 + k × b ( a , b ) ⩽ ⌊ n l ⌋ 1 ⩽ y 0 − k × a ( a , b ) ⩽ ⌊ m l ⌋ \begin{cases} 1\leqslant x_0+k\times \dfrac b{(a,b)}\leqslant \left\lfloor\dfrac nl\right\rfloor\\ 1\leqslant y_0-k\times \dfrac{a}{(a,b)}\leqslant \left\lfloor\dfrac ml\right\rfloor \end{cases} ⎩⎪⎨⎪⎧1⩽x0+k×(a,b)b⩽⌊ln⌋1⩽y0−k×(a,b)a⩽⌊lm⌋ 你分别讨论了两个式子化简时乘除的东西的正负号,写出了这样的代码。
#include
using namespace std;
#define int long long
int exgcd(int a,int b,long long &x,long long &y) {
if(!b) return x=1,y=0,a;
int d=exgcd(b,a%b,y,x);
y-=a/b*x; return d;
}
int Dioequ(int a,int b,int c,long long &x,long long &y){
int d=exgcd(a,b,x,y);
int k=c/d; x*=k;y*=k;
return d;
}
int n,m,l;
signed main(){
scanf("%lld%lld%lld",&n,&m,&l);
int a=m+l,b=-n-l,c=n-m,x1,y1;
int gcd=Dioequ(a,b,c,x1,y1);
double la=(double)(1-x1)/(b/gcd);//计算第一条式子的左侧<=1整理后的结果
double lb=(double)(1-y1)/(-a/gcd);//计算第二条式子的左侧<=1整理后的结果
double ra=(double)(n/l-x1)/(b/gcd);//计算第以条式子的左侧>=n/l整理后的结果
double rb=(double)(m/l-y1)/(-a/gcd);//计算第二条式子的左侧>=m/l整理后的结果
if(-a/gcd<0) swap(lb,rb);//判断正负
if(b/gcd<0) swap(la,ra);
int up=(int)floor(min(ra,rb));
int dn=(int)ceil(max(la,lb));
if(min(ra,rb)<max(la,lb)) return printf("0\n")==1;//判断无解
printf("%lld\n",up-dn+1);
return 0;
}
% 你对这份代码信心满满,并决定这份代码交上去,本以为需要调半天,结果发现过了……