寒假集训日志(三)——数论

  今天听得简直要崩溃。。。没听懂啥。。。

  主要内容:

  1.欧几里得(稍微懂了点)

  2.中国剩余定理( 稍微懂了点)

  3.博弈( 看智商的玩意儿)

(一)欧几里得算法(及其扩展算法)

  欧几里得定理就是gcd(辗转相除法)的原理(不懂,只会用)。

  扩展算法的运用大概就是用来解一个 ax + by = gcd( a, b )的不定方程。

  大致证明步骤:  将a 替换为b, 将b 替换为gcd(b, a%b),又gcd(a,b) = gcd( b, a%b),就可以化为一个等式巴拉巴拉的。然后算法实现的花就用递归:

  

//第一种,之后修改所得的 x , y 就是一组特解,通解的话直接根据系数在特解的基础上加减就好了
void exGcd ( ll a, ll b, ll &x , ll &y){
    if( b== 0) {
        x =1 ; y = 0;
        return ;
    }
    else{
    exGcd ( b, a% b, x, y);
    ll t = x;
    x = y;
    y = t - a/b* y;
    }
}
//第二种,简短,d最后得到的是 a, b的最大公约数 。 另外,注意这段代码 x, y需要交换位置的部分
void gcd( int a, int b, int &d, int &x, int &y){
    if(!b) { d = a ; x =1 ; y = 0 ;}
    else { gcd( b, a%b, d, y, x);
    y-= x*(a/b);
  }
}

今天真正会做的也就3道。。。两道是这个算法的同类题,就放一道把。。。

A - 青蛙的约会
Time Limit:1000MS     Memory Limit:10000KB     64bit IO Format:%I64d & %I64u
Submit Status

Description

两只青蛙在网上相识了,它们聊得很开心,于是觉得很有必要见一面。它们很高兴地发现它们住在同一条纬度线上,于是它们约定各自朝西跳,直到碰面为止。可是 它们出发之前忘记了一件很重要的事情,既没有问清楚对方的特征,也没有约定见面的具体位置。不过青蛙们都是很乐观的,它们觉得只要一直朝着某个方向跳下 去,总能碰到对方的。但是除非这两只青蛙在同一时间跳到同一点上,不然是永远都不可能碰面的。为了帮助这两只乐观的青蛙,你被要求写一个程序来判断这两只 青蛙是否能够碰面,会在什么时候碰面。
我们把这两只青蛙分别叫做青蛙A和青蛙B,并且规定纬度线上东经0度处为原点,由东往西为正方向,单位长度1米,这样我们就得到了 一条首尾相接的数轴。设青蛙A的出发点坐标是x,青蛙B的出发点坐标是y。青蛙A一次能跳m米,青蛙B一次能跳n米,两只青蛙跳一次所花费的时间相同。纬 度线总长L米。现在要你求出它们跳了几次以后才会碰面。

Input

输入只包括一行5个整数x,y,m,n,L,其中x≠y < 2000000000,0 < m、n < 2000000000,0 < L < 2100000000。

Output

输出碰面所需要的跳跃次数,如果永远不可能碰面则输出一行"Impossible"

Sample Input

1 2 3 4 5

Sample Output

4
我的代码:
//这个题目要注意一下long long
#include<iostream> #include<cstdio> #include<cmath> #include<algorithm> typedef long long ll; using namespace std; ll gcd (ll a ,ll b){ return b==0?a: gcd( b, a%b); } void exGcd ( ll a, ll b, ll &x , ll &y){ if( b== 0) { x =1 ; y = 0; return ; } else{ exGcd ( b, a% b, x, y); ll t = x; x = y; y = t - a/b* y; } } int main(){ ll x, y, m, n, L, k, t; cin>>x>>y>>m>>n>>L; ll a = n - m; ll b = L; ll c = x - y; ll d =gcd ( a,b); if( c % d !=0 ){ cout<<"Impossible"<<endl; return 0; } a /=d; b /= d; c /= d; exGcd ( a, b , t, k ); t = c* t%b; if( t < 0 ) t+=b; //printf("%I64d ", t); cout<< t<<endl; return 0; }

(二) 中国剩余定理:

  就是解决一个似乎是叫韩信点兵的问题。(以下转自http://yzmduncan.iteye.com/blog/1323599/)

  互质版的很好懂,就是直接一公式就出来了。

MOD M

  非互质版的比较麻烦,代码也有点乱,就是采取合并的方法,暂时没弄懂,需好好体会。

中国剩余定理

     中国剩余定理是中国古代求解一次同余方程组的方法,是数论中的一个重要定理。

     设m1,m2,m3,...,mk是两两互素的正整数,即gcd(mi,mj)=1,i!=j,i,j=1,2,3,...,k.

则同余方程组:

x = a1 (mod n1)

x = a2 (mod n2)

...

x = ak (mod nk)

模[n1,n2,...nk]有唯一解,即在[n1,n2,...,nk]的意义下,存在唯一的x,满足:

x = ai mod [n1,n2,...,nk], i=1,2,3,...,k。

解可以写为这种形式:

x = sigma(ai* mi*mi') mod(N)

      其中N=n1*n2*...*nk,mi=N/ni,mi'为mi在模ni乘法下的逆元。

中国剩余定理非互质版

    中国剩余定理求解同余方程要求模数两两互质,在非互质的时候其实也可以计算,这里采用的是合并方程的思想。下面是详细推导。

互质版:

    #include <iostream>  
    #include <cstdio>  
    #include <cstring>  
    using namespace std;  
    typedef __int64 int64;  
    int64 a[15],b[15];  
      
    int64 Extend_Euclid(int64 a, int64 b, int64&x, int64& y)  
    {  
        if(b==0)  
        {  
            x=1,y=0;  
            return a;  
        }  
        int64 d = Extend_Euclid(b,a%b,x,y);  
        int64 t = x;  
        x = y;  
        y = t - a/b*y;  
        return d;  
    }  
    //求解模线性方程组x=ai(mod ni)  
    int64 China_Reminder(int len, int64* a, int64* n)  
    {  
        int i;  
        int64 N = 1;  
        int64 result = 0;  
        for(i = 0; i < len; i++)  
            N = N*n[i];  
        for(i = 0; i < len; i++)  
        {  
            int64 m = N/n[i];  
            int64 x,y;  
            Extend_Euclid(m,n[i],x,y);  
            x = (x%n[i]+n[i])%n[i];  
            result = (result + m*a[i]*x%N)%N;  
        }  
        return result;  
    }  
      
    int main()  
    {  
        int n;  
        while(scanf("%d",&n)!=EOF)  
        {  
            for(int i = 0; i < n; i++)  
                scanf("%I64d %I64d",&a[i],&b[i]);  
            printf("%I64d
",China_Reminder(n,b,a));  
        }  
        return 0;  
    }  

非互质版:

    /** 
    中国剩余定理(不互质) 
    */  
    #include <iostream>  
    #include <cstdio>  
    #include <cstring>  
    using namespace std;  
    typedef __int64 int64;  
    int64 Mod;  
      
    int64 gcd(int64 a, int64 b)  
    {  
        if(b==0)  
            return a;  
        return gcd(b,a%b);  
    }  
      
    int64 Extend_Euclid(int64 a, int64 b, int64&x, int64& y)  
    {  
        if(b==0)  
        {  
            x=1,y=0;  
            return a;  
        }  
        int64 d = Extend_Euclid(b,a%b,x,y);  
        int64 t = x;  
        x = y;  
        y = t - a/b*y;  
        return d;  
    }  
      
    //a在模n乘法下的逆元,没有则返回-1  
    int64 inv(int64 a, int64 n)  
    {  
        int64 x,y;  
        int64 t = Extend_Euclid(a,n,x,y);  
        if(t != 1)  
            return -1;  
        return (x%n+n)%n;  
    }  
      
    //将两个方程合并为一个  
    bool merge(int64 a1, int64 n1, int64 a2, int64 n2, int64& a3, int64& n3)  
    {  
        int64 d = gcd(n1,n2);  
        int64 c = a2-a1;  
        if(c%d)  
            return false;  
        c = (c%n2+n2)%n2;  
        c /= d;  
        n1 /= d;  
        n2 /= d;  
        c *= inv(n1,n2);  
        c %= n2;  
        c *= n1*d;  
        c += a1;  
        n3 = n1*n2*d;  
        a3 = (c%n3+n3)%n3;  
        return true;  
    }  
      
    //求模线性方程组x=ai(mod ni),ni可以不互质  
    int64 China_Reminder2(int len, int64* a, int64* n)  
    {  
        int64 a1=a[0],n1=n[0];  
        int64 a2,n2;  
        for(int i = 1; i < len; i++)  
        {  
            int64 aa,nn;  
            a2 = a[i],n2=n[i];  
            if(!merge(a1,n1,a2,n2,aa,nn))  
                return -1;  
            a1 = aa;  
            n1 = nn;  
        }  
        Mod = n1;  
        return (a1%n1+n1)%n1;  
    }  
    int64 a[1000],b[1000];  
    int main()  
    {  
        int i;  
        int k;  
        while(scanf("%d",&k)!=EOF)  
        {  
            for(i = 0; i < k; i++)  
                scanf("%I64d %I64d",&a[i],&b[i]);  
            printf("%I64d
",China_Reminder2(k,b,a));  
        }  
        return 0;  
    }  

题目就不给出了,基本一眼就可以看出来,也很难有什么改变。

(三)博弈论

  此类题变幻无穷。。。及其考智商,只是任何时候都别忘了dp。。

  另外,打表的方法一定要学会。

   直接加上两句话就行了:

  freopen("input.txt", "r", stdin);   //将文件中的数据输入

  freopen("output.txt", "w", stdout);  //将程序中的输出输出到文件中

原文地址:https://www.cnblogs.com/topW2W/p/5156283.html