51Nod 提高组400+试题 第四组 种田

题目大意

%   给定一个面积 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} 1n,m,l1013

题解

%   你拿到这道题,想了很久,发现只会考虑朴素算法,你尝试着枚举一行放 x x x 个正方形矩阵,一列放 y y y 个矩阵,其中 i × l ⩽ n , j × l ⩽ m i\times l\leqslant n,j\times l\leqslant m i×ln,j×lm 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+1nxl=y+1myl  你考虑到除法可能会丢失精度,你化简了一下,得到了一个优秀的式子和不优秀的暴力。
( n − x l ) ( y + 1 ) = ( m − y l ) ( x + 1 ) (1) (n-xl)(y+1)=(m-yl)(x+1)\tag{1} (nxl)(y+1)=(myl)(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+nxylxl=mx+mxylyl

%   化简,整理,尝试让左边只剩下 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+mn

%   于是,你得到了一个优秀少许的做法,枚举 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+(nl)y=nm  你定眼一看,发现 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=nl,c=nm  问题变成了关于 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=y0k×(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} 1x0+k×(a,b)bln1y0k×(a,b)alm  你分别讨论了两个式子化简时乘除的东西的正负号,写出了这样的代码。

#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;
}

%   你对这份代码信心满满,并决定这份代码交上去,本以为需要调半天,结果发现过了……

你可能感兴趣的:(扩展欧几里得,数论,扩展欧几里得,数论)