【拓展歐幾里得】方程的解
【題目描述】
給出一個二元一次方程 ax+by=c,其中 x、y 是未知數,求它的正整數解的
數量。
【輸入格式】
第一行一個整數 T,表示有 T 組資料。接下來 T 行,每行 3 個整數 a、b、c。
【輸出格式】
輸出 T 行,每行一個數,表示方程解的數量。如果正整數解的數量比
65535 還多,輸出“ZenMeZheMeDuo”。
【樣例輸入】
3
-1 -1 -3
1 1 65536
1 1 65537
【樣例輸出】
2
65535
ZenMeZheMeDuo
【資料規模與約定】
20%的資料,a=b=1
40%的資料,T≤100,1≤a,b,c≤1000
另 20%的資料,a+b=c,1≤a,b,c≤1,000,000
另 20%的資料,1≤a,b,c≤1,000,000
100%的資料,T≤10000,-1,000,000≤a,b,c≤1,000,000
【題解】
核心為拓展歐幾里得:
1、若a或b等於0看不為零的是否整除c 若c為0看a,b是否異號;
2、若兩者為負就轉化為一者為負;
3、若a,b異號,假設a<0,b>0則迴圈x至多b次;
4、若a,b同號但不與c同號,則方程無解 由此若a,b,c均為負就可以全部化為正;
以上為特殊情況,答案只可能是0或者無窮多。以下情況中,a,b,c均為正數。
5、列舉x,從1開始,滿足c-ax>=0,尋找某一時刻(c-ax)%b==0,則找到了方程的一組解;
6、這一組解一定是x最小y最大的那組解,對於相鄰的每兩組解,y都減去了a和b的最小公倍數除以b,觀察y能夠減去多少個這個數;
再注意特判,注意處理負數情況即可。
#include<iostream>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<vector>
#define ll long long
#define re register
#define il inline
#define fp(i,a,b) for(re int i=a;i<=b;i++)
#define fq(i,a,b) for(re int i=a;i>=b;i--)
using namespace std;
il int gi()
{
re int x=0;
re short int t=1;
re char ch=getchar();
while((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
if(ch=='-') t=-1,ch=getchar();
while(ch>='0'&&ch<='9') x=x*10+ch-48,ch=getchar();
return x*t;
}
void exgcd(int a,int b,int c,long long &x,long long &y,int &d)//拓展歐幾里得演算法模板
{
if(!b) y=0,d=a,x=c/a;
else exgcd(b,a%b,c,y,x,d),y-=(a/b)*x;
}
void f()
{
int a,b,c,d;
ll x,y;
a=gi();b=gi();c=gi();
if(!a && !b)//特判a、b都等於0的情況
{
if(!c)
printf("ZenMeZheMeDuo\n");
else
printf("0\n");
return;
}
if(c<0) a=-a,b=-b,c=-c;
bool fana=0;
if(a<0) a=-a,fana=1;
bool fanb=0;
if(b<0) b=-b,fanb=1;//處理掉負數,有負數會WA
exgcd(a,b,c,x,y,d);//弄組解出來
if(a*x+b*y!=c)//一組解都沒有就gg了
{
printf("0\n");
return;
}
if(fana) x=-x,a=-a;
if(fanb) y=-y,b=-b;//把負數弄回來
if(a==0)
{
if(y<=0) printf("0\n");
else printf("ZenMeZheMeDuo\n");
return;
}
if(b==0)
{
if(x<=0) printf("0\n");
else printf("ZenMeZheMeDuo\n");
return;
}
if(a<0 && b>0 || a>0 && b<0)
{
printf("ZenMeZheMeDuo\n");
return;
}//特判很重要
if(a<0) a=-a,b=-b,c=-c;
a/=d;b/=d;c/=d;//d是最大公約數
x%=b;
if(x<=0) x+=b;
if(x==0) x+=b;//保證模出的是正數
y=(c-a*x)/b;
ll miny=y%a;//求出y的最小值
if(miny<0) miny+=a;
if(miny==0) miny+=a;
int res;
if(miny>y) res=0;
else res=(y-miny)/a+1;//求出解的個數
if(res>65535) printf("ZenMeZheMeDuo\n");
else printf("%d\n",res);
}
int main()
{
freopen("fuction.in","r",stdin);
freopen("fuction.out","w",stdout);
int T;
T=gi();
while(T--)
f();
fclose(stdin);
fclose(stdout);
return 0;
}
順便再記一下爆搜+剪枝的方法,雖然慢,但不容易打錯。。。
#include<cstdio>
#include<algorithm>
#define ll long long
using namespace std;
ll T,a,b,c,neg;
ll gcd(ll x,ll y)
{
if (y==0) return x;
return gcd(y,x%y);
}
ll gi()
{
ll x=0,w=1;char ch=getchar();
while ((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
if (ch=='-') w=-1,ch=getchar();
while (ch>='0'&&ch<='9')
{
x=x*10+ch-'0';
ch=getchar();
}
return x*w;
}
int main()
{
//freopen("fuction.in","r",stdin);
//freopen("fuction.out","w",stdout);
T=gi();
while (T--)
{
a=gi();b=gi();c=gi();neg=0;
if (a==0&&b==0&&c==0)
{
printf("ZenMeZheMeDuo\n");
continue;
}
if (c==0)
{
if (a==0||b==0) printf("0\n");
else if ((a<0&&b>0)||(a>0&&b<0)) printf("ZenMeZheMeDuo\n");
else printf("0\n");
continue;
}
if (a==0||b==0)
{
if (a==0&&b==0) printf("0\n");
else
{
ll t=(a==0)?b:a;
if ((t>0&&c<0)||(t<0&&c>0)) printf("0\n");
else
{
if (t<0) t=-t,c=-c;
if (c%t==0) printf("ZenMeZheMeDuo\n");
else printf("0\n");
}
}
continue;
}
if (a<0) neg++;
if (b<0) neg++;
if (c<0) neg++;
if (a>0&&b>0&&c>0&&a+b==c)
{
printf("1\n");
continue;
}
if (neg>=2) a=-a,b=-b,c=-c,neg=3-neg;
if (neg==1)
{
if (c<0)
{
printf("0\n");
continue;
}
if (a<0||b<0)
{
if (b<0) swap(a,b);
bool key=0;
for (ll x=1;x<=b;x++)
if ((c-a*x)%b==0)
{
key=1;break;
}
if (key==1) printf("ZenMeZheMeDuo\n");
else printf("0\n");
continue;
}
}
if (neg==0)
{
ll X,Y;
for (X=1;c-a*X>0;X++)
if ((c-a*X)%b==0) break;
if (c-a*X<=0) printf("0\n");
else
{
Y=(c-a*X)/b;
ll num=a/gcd(a,b);
ll ans=Y/num+1;
if (Y%num==0) ans--;
if (ans>65535) printf("ZenMeZheMeDuo\n");
else printf("%lld\n",ans);
}
}
}
return 0;
}