洛谷P5656 【模板】二元一次不定方程(exgcd)
這是一道模板題(bushi)
首先,說實話,我感覺做這道題比較吃力,畢竟我是剛剛背過\(exgcd\)程式碼,並且只是小小的對找正整數解有所瞭解,所以寫了半天才做出來。做這種數論題確實是對計算能力和邏輯思維的考驗,而且從難度上看,我這個蒟蒻能做出綠題,也有點小小的成就感。
今天開始停課備戰\(CSP\),下午在機房打板子,打到\(exgcd\),突然發現我板子題還沒\(A\),其他的拓展題倒是做了不少,遂開始切板子題。
思路
其實思路非常簡單,就是程式碼實現細節真的比較多(而且我貌似寫的很麻煩,寫了足足有130多行)。首先用真板子來求出一組特解,然後判斷是否有解。在找最小整數解的時候,要特判一波:如果有一組解能滿足\(x\)
那麼首先最好寫的就是求出的特解都是正整數的情況,只需要用一波取模的常規操作求出一個最小正整數解,那麼同時另一個解就是最大正整數解。即\(x\)最小時,\(y\)最大。就這樣求出四個最值。個數可以通過最大解和最小解中間差了幾個\(a/d\)或\(b/d\)即可(原理就是求最小正整數解的操作,詳情可見【青蛙的約會】)。然後中間稍微手玩一下,就能找到個數與最值之間的關係。
ll x1=(x1%(b/d)+(b/d))%(b/d); ll y1=(y1%(a/d)+(a/d))%(a/d);//求出最小值 if(x1==0){//特判,必須是正整數 x1=b/d; } if(y1==0){ y1=a/d; } ll x2=(c-y1*b)/a; ll y2=(c-x1*a)/b;//根據最小值推出最大值 ll sum; if(x2%(b/d)==0){//手玩即可找到規律,推導為小學數學水平 sum=x2/(b/d); x1=a/d; } else{ sum=x2/(b/d)+1; x1=x2%(b/d); }
那麼接下來就是\(x\)和\(y\)這一組特解中有一個為非正整數的情況了,雖然這種情況相對複雜,但是原理是一樣的。假設\(x\)是負的,那麼\(y\)必然是正的,否則不可能得到正數結果。那麼我們就讓\(x\)不斷地加上\(b/d\),為保持等式成立,也要讓\(y\)同時減去\(a/d\)。這樣將\(x\)變為正數。如果\(x\)變為正數後\(y\)變為了負數,那麼就說明不可能有正整數解,這時候只要進行一步常規取模操作,輸出最小正整數解即可。但是如果\(x\)變為正數後\(y\)依然是正數,就相當於變回了第一種情況,再操作即可。
思路就是這麼簡單,但是程式碼實現的確有難度。首先我們思考\(x\)需要幾個\(b/d\)
if(x<=0){
x=-x;
int k=x/(b/d)+1;
x-=k*(b/d);
x=-x;
y-=k*(a/d);
if(y>0){//有正整數解
ll x1=x,y2=y,sum,y1;
if(y2%(a/d)==0){
sum=y2/(a/d);
y1=a/d;
}
else{
sum=y2/(a/d)+1;
y1=y2%(a/d);
}
ll x2=(c-y1*b)/a;
printf("%lld %lld %lld %lld %lld\n",sum,x1,y1,x2,y2);
continue;
}
else{//無正整數解
ll x1=(xx%(b/d)+(b/d))%(b/d);
ll y1=(yy%(a/d)+(a/d))%(a/d);
if(x1==0){
x1=b/d;
}
if(y1==0){
y1=a/d;
}
printf("%lld %lld\n",x1,y1);
continue;
}
}
做完了
程式碼
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstdlib>
#include<ctime>
#include<cstring>
#include<queue>
using namespace std;
typedef long long ll;
int T;
inline ll read(){
ll x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
while(isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
ll exgcd(ll a,ll b,ll &x,ll &y){
if(b==0){
x=1;y=0;
return a;
}
ll d=exgcd(b,a%b,x,y);
ll z=x;x=y;y=z-y*(a/b);
return d;
}
int main()
{
scanf("%d",&T);
while(T--){
ll a,b,c,x,y;
a=read();b=read();c=read();
ll d=exgcd(a,b,x,y);
if(c%d!=0){
printf("-1\n");
continue;
}
else{
x*=c/d,y*=c/d;
ll xx=x,yy=y;
if(x<=0){
x=-x;
int k=x/(b/d)+1;
x-=k*(b/d);
x=-x;
y-=k*(a/d);
if(y>0){//有正整數解
ll x1=x,y2=y,sum,y1;
if(y2%(a/d)==0){
sum=y2/(a/d);
y1=a/d;
}
else{
sum=y2/(a/d)+1;
y1=y2%(a/d);
}
ll x2=(c-y1*b)/a;
printf("%lld %lld %lld %lld %lld\n",sum,x1,y1,x2,y2);
continue;
}
else{//無正整數解
ll x1=(xx%(b/d)+(b/d))%(b/d);
ll y1=(yy%(a/d)+(a/d))%(a/d);
if(x1==0){
x1=b/d;
}
if(y1==0){
y1=a/d;
}
printf("%lld %lld\n",x1,y1);
continue;
}
}
if(y<=0){
y=-y;
int k=y/(a/d)+1;
y-=k*(a/d);
y=-y;
x-=k*(b/d);
if(x>0){//有正整數解 (y為最小整數解
ll y1=y,x2=x,x1,sum;
if(x2%(b/d)==0){
sum=x2/(b/d);
x1=b/d;
}
else{
sum=x2/(b/d)+1;
x1=x2%(b/d);
}
ll y2=(c-x1*a)/b;
printf("%lld %lld %lld %lld %lld\n",sum,x1,y1,x2,y2);
continue;
}
else{
ll x1=(xx%(b/d)+(b/d))%(b/d);
ll y1=(yy%(a/d)+(a/d))%(a/d);
if(x1==0){
x1=b/d;
}
if(y1==0){
y1=a/d;
}
printf("%lld %lld\n",x1,y1);
continue;
}
}
ll x1=(x1%(b/d)+(b/d))%(b/d);
ll y1=(y1%(a/d)+(a/d))%(a/d);
if(x1==0){
x1=b/d;
}
if(y1==0){
y1=a/d;
}
ll x2=(c-y1*b)/a;
ll y2=(c-x1*a)/b;
ll sum;
if(x2%(b/d)==0){
sum=x2/(b/d);
x1=a/d;
}
else{
sum=x2/(b/d)+1;
x1=x2%(b/d);
}
}
}
return 0;
}
中間出了兩個小鍋,耗費了我大量時間:一個是我忘記判x>0&&y>0的情況了,可能是因為這種情況相比於其它的情況太水了,所以忘記了。第二個,由於y<0和x<0的程式碼絕大部分都是一樣的,只不過需要該兩個變數名,所以我直接複製貼上之後一個個改變數名。結果漏改了一個,導致出鍋。