1. 程式人生 > >【拓展歐幾里得】方程的解

【拓展歐幾里得】方程的解

【題目描述】
給出一個二元一次方程 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;
}