51Nod 提高組400+試題 第四組 種田

題目大意

%  給定一個面積 n×mn\times m 的區域,在其中放若干個邊長爲 ll 的正方形矩陣,使得每個正方形矩陣的邊緣與和其相鄰的正方形矩陣的邊緣(或相鄰的整個大的區域的邊緣)的距離都相等,求合法的方案數。
  數據範圍 1n,m,l10131\leqslant n,m,l\leqslant 10^{13}

題解

%  你拿到這道題,想了很久,發現只會考慮樸素算法,你嘗試着枚舉一行放 xx 個正方形矩陣,一列放 yy 個矩陣,其中 i×ln,j×lmi\times l\leqslant n,j\times l\leqslant mi,ji,j 都是正整數,你發現,這對 l,r\langle l,r\rangle 合法的充要條件是
nxlx+1=myly+1\frac{n-xl}{x+1}=\frac{m-yl}{y+1}  你考慮到除法可能會丟失精度,你化簡了一下,得到了一個優秀的式子和不優秀的暴力。
(nxl)(y+1)=(myl)(x+1)(1)(n-xl)(y+1)=(m-yl)(x+1)\tag{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) 式的 xx,都有唯一成立的 yy。你嘗試把 (1)(1) 式拆開
ny+nxylxl=mx+mxylylny+n-xyl-xl=mx+m-xyl-yl

%  化簡,整理,嘗試讓左邊只剩下 yy
y=(m+l)x+mnn+ly=\frac{(m+l)x+m-n}{n+l}

%  於是,你得到了一個優秀少許的做法,枚舉 xx,判斷 yy 是不是正整數。

for(long long x=1;x*l<=n;x++)
	if(((m+l)*x+m-n)%(n+l)==0) ++ans;

%  你繼續化簡,發現沒有任何餘地,或者說,你的腦子根本不給你機會想到比這更優秀的做法,你開始放棄這種想法,並在爲幾秒鐘之前的自己白忙活了而感到幸災樂禍。你繼續考慮 (1)(1) 式,你嘗試將它變得更加優美一些。
(m+l)x+(nl)y=nm(m+l)x+(-n-l)y=n-m  你定眼一看,發現 n,m,ln,m,l 都是已知的,你令 a=m+l,b=nl,c=nma=m+l,b=-n-l,c=n-m  問題變成了關於 x,yx,y 的一元二次方程ax+by=cax+by=c  的正整數解的個數,再 浪費時間 想想,你想到了擴展歐幾里得算法,並用它求出了不定方程的特解 x0,y0x_0,y_0,還導出了所有解的通式
{x=x0+k×b(a,b)y=y0k×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,yx,y 均爲正整數的整數 kk 的個數。將這個限制代入,得
{1x0+k×b(a,b)nl1y0k×a(a,b)ml\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}  你分別討論了兩個式子化簡時乘除的東西的正負號,寫出了這樣的代碼。

#include<bits/stdc++.h>
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;
}

%  你對這份代碼信心滿滿,並決定這份代碼交上去,本以爲需要調半天,結果發現過了……

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章