数学——整除和剩余

因为ACM/ICPC中有些题目是关于数论的,特别是解线性同余方程,所以有必要准备下这方面的知识。关于这部分知识,我先后翻看过很多资料,包括陈景润的《初等数论》、程序设计竞赛例题解、“黑书”和很多网上资料,个人认为讲的最好最透彻的是《算法导论》中的有关章节,看了之后恍然大悟。经过几天的自学,自己觉得基本掌握了其中的“奥妙”。拿出来写成文章。

那么什么是线性同余方程?对于方程:ax≡b(mod   m),a,b,m都是整数,求解x 的值。

解题例程:pku1061 青蛙的约会 解题报告

符号说明:

                  mod表示:取模运算

                  ax≡b(mod   m)表示:(ax - b) mod m = 0,即同余

                  gcd(a,b)表示:a和b的最大公约数

求解ax≡b(mod n)的原理:

对于方程ax≡b(mod n),存在ax + by = gcd(a,b),x,y是整数。而ax≡b(mod n)的解可以由x,y来堆砌。具体做法,见下面的MLES算法。

第一个问题:求解gcd(a,b)

定理一:gcd(a,b) = gcd(b,a mod b)

实现:古老的欧几里德算法

int Euclid(int a,int b)
{
if(b == 0)
      return a;
else
      return Euclid(b,mod(a,b));
}

附:取模运算

int mod(int a,int b)
{
if(a >= 0)
      return a % b;
else
      return a % b + b;
}

第二个问题:求解ax + by = gcd(a,b)

定理二:gcd(b,a mod b) = b * x' + (a mod b) * y'

                                           = b * x' + (a - a / b * b) * y'

                                           = a * y' + b * (x' - a / b *      y')

                                           = a * x + b * y

                  则:x = y'

                         y = x' - a / b * y'

实现:

triple Extended_Euclid(int a,int b)
{
triple result;
if(b == 0)
{
      result.d = a;
      result.x = 1;
      result.y = 0;
}
else
{
      triple ee = Extended_Euclid(b,mod(a,b));
      result.d = ee.d;
      result.x = ee.y;
      result.y = ee.x - (a/b)*ee.y;
}
return result;
}

附:三元组triple的定义

struct triple
{
int d,x,y;
};

第三个问题:求解ax≡b(mod n)

实现:由x,y堆砌方程的解

int MLES(int a,int b,int n)
{
triple ee = Extended_Euclid(a,n);
if(mod(b,ee.d) == 0)
      return mod((ee.x * (b / ee.d)),n / ee.d);
else
      return -1;
}//返回-1为无解,否则返回的是方程的最小解

说明:ax≡b(mod n)解的个数:

           如果ee.d 整除 b 则有ee.d个解;

           如果ee.d 不能整除 b 则无解。

第四个问题:求解a≡bi(mod m[i]),用

中国剩余定理和一次同余方程组的通用解法

今天参加了一家做图形软件的公司的笔试,出了这样一道题:一堆鸡蛋,3个3个数余2,5个5个数余1,7个7个数余6,问这堆鸡蛋最少有多少个?

    作为数学专业出身的我看到这道题当即笑了,因为这是一个很经典的一次同余方程的问题。当然很快就用曾经记得的算法给出了答案101,但我觉得还不过瘾,又因为觉得这类问题对于搞计算机的实在很重要,所以又回忆了一下初等数论的相关理论知识,特此总结一下!

    对于这个问题用数学的语言来描述就是:

        N = 2(mod 3) = 1(mod 5) = 6(mod 7);

    求解最小N。

    由一首诗可以轻易得出求解算法:

   “ 三人同行七十稀,五树梅花甘一枝, 七子团圆正半月,除百零五便得知。”

    即解为:N = 2*70+1*21+6*15-105*n.(n为使得N不为负的最大正整数,这里就是1了)

    那么我们是怎么得到这个公式的呢?

    其实早在几千年前中国的一本《孙子算经》就已经提及这个问题的解法了,原文为:

“今有物,不知其数,三三数之,剩二,五五数之,剩三,七七数之,剩二,问物几何?” 
    答曰:“二十三”
    术曰:“三三数之剩二,置一百四十,五五数之剩三,置六十三,七七数之剩二,置三十,并之,得二百三十三,以二百一十减之,即得。凡三三数之剩一,则置七十,五五数之剩一,则置二十一,七七数之剩一,则置十五,即得。”

    《孙子算经》的“物不知数”题虽然开创了一次同余式研究的先河,但由于题目比较简单,甚至用试猜的方法也能求得,所以尚没有上升到一套完整的计算程序和理论的高度。真正从完整的计算程序和理论上解决这个问题的,是南宋时期的数学家秦九韶。秦九韶在他的《数书九章》中提出了一个数学方法“大衍求一术”(其实就是后来数学王子高斯提出的解同余方程的方法),系统地论述了一次同余式组解法的基本原理和一般程序。

     秦九韶为什么要把他的这一套计算程序和基本原理称为“大衍求一术”呢?这是因为其计算程序的核心问题是要“求一”。所谓“求一”,通俗他说,就是求“一个数的多少倍除以另一个数,所得的余数为一”。那么为什么要“求一”呢?我们可以从“物不知数”题的几个关键数字70、21、15中找到规律。

    我们可以看出解此题的关键在于如何确定70、21、15这3个数(105 = 3*5*7)。现简述如下:

    归结为一句话就是求3、5、7三个数两两组合相乘的k倍模另外一个数而余1的数 。这样说可能有点抽象,下面具体讲解下大家就会明白了:

    首先对于5、7两个数,即为:

            70 = 2*5*7  = 1(mod 3)

    其中k为2,所以很容易求出第一个70,同样的道理可以求出:

            21 = 3*7 = 1(mod 5)

            15 = 3*5 = 1(mod 7)

 

    理解了上例解法,我们不难得出著名的中国剩余定理(孙子定理)的一般数学形式了:

    一次同余方程组

             x = a1(mod m1)

             x = a2(mod m2)

             ...

             x = ak(mod mk)

    若m1,m2,......,mk这些数两两互质,且定义miMi = m1m2......mk以及CiMi = 1 (mod mi)。则此一次同余方程组的解为:

        x = a1M1C1 + a2M2C2 + ......+akMkCk (mod m)。

 

    所以我们可以很容易的把这个面试题推广为:

    一堆鸡蛋,3个3个数余a1,5个5个数余a2,7个7个数余a3,问这堆鸡蛋最少有多少个?

    甚至推广为:

    一堆鸡蛋,n1个n1个数余a1,n2个n2个数余a2,n3个n3个数余 a,....., nm个nm个数余am,问这堆鸡蛋最少有多少个?(其中n1,n2,....., nm互质)


推论1: 方程ax≡b(mod n)对于未知量x有解,当且仅当gcd(a,n) | b (b能被gcd(a,b)整除)。

即:若gcd(a,n)|b ==> 有一个解x,使得ax≡b(mod n).   

推论2:方程ax≡b(mod n)或者对模n有d个不同的解(有解当且仅当d | b),其中d=gcd(a,n),或者无解。   

定理1:设d=gcd(a,n),假定对整数x'y'满足d=ax'+by'(比如用扩展Euclid算法求出的一组解)。如果d | b,则该方程对模n有d个不同的解,方程ax≡b(mod n)有一个解x0满足x0=x'*(b/d) mod n 通解为xi = x0 + i*(n / d)(d = 0, 1, 2, ..., d - 1)。特别的设e=x0+n,方程ax≡b(mod n)的最小整数解x1=e mod (n/d),最大整数解x2=x1+(d-1)*(n/d)

定理2:假设方程ax=b(mod n)有解,且x0是方程的任意一个解,则该方程对模n恰有d个不同的解(d=gcd(a,n)),分别为:xi=x0+i*(n/d) mod n 。

 

类似地: 可以求ax + by = c的整数解(x, y)。==> ax ≡ c(mod b), d = gcd(a, b) | c 时有解。有一个解x0 = x’* (c / d) mod b,  y0 = (-a * x0 - c) / b; 通解xi = x0 + i * (b / d), yi = y0 - i * (a / d) (i = 012, ..., d - 1)。


附:扩展欧几里得代码

int exgcd(int a, int b, int& x, int& y)
{
if(b == 0)
{
x = 1;
y = 0;
return a;
}
int r = exgcd(b, a%b, x, y);
int t = x;
x = y;
y = t - a/b* y;
return r;
}


        模板一(解ax = b (mod n) 的最小解)

#include 
 
typedef long long LL;
 
// return d = gcd(a, b) = ax + by
inline LL extended_euclid(const LL& a, const LL& b, LL &x, LL &y)
{
    if (b == 0)
    {
        x = 1, y = 0;
        return a;
    }
    LL d = extended_euclid(b, a % b, x, y);
    LL x1;
    x1 = x, x = y, y = x1 - a / b * y;
    return d;
}
 
// return ax = b (mod n) 的最小解
inline LL modular_linear_equation_solver(const LL &a, const LL &b, const LL &n)
{
    LL d, x, y;
    d = extended_euclid(a, n, x, y);    // a 与 n 的最大公约数d
    if (b % d)
        return -1;
    LL x0 = x * (b / d) % n + n;    // x0 * d 是 ax = b (mod n) 的一个解
    return x0 % (n / d);            // ax = b (mod n) 的最小解
}


模板一的例题

C Looooops

Time Limit: 1000ms
Memory Limit: 65536KB
64-bit integer IO format: %lld      Java class name: Main
Submit Status
A Compiler Mystery: We are given a C-language style for loop of type
for (variable = A; variable != B; variable += C)

  statement;

I.e., a loop which starts by setting variable to value A and while variable is not equal to B, repeats statement followed by increasing the variable by C. We want to know how many times does the statement get executed for particular values of A, B and C, assuming that all arithmetics is calculated in a k-bit unsigned integer type (with values 0 <= x < 2 k) modulo 2 k.

Input

The input consists of several instances. Each instance is described by a single line with four integers A, B, C, k separated by a single space. The integer k (1 <= k <= 32) is the number of bits of the control variable of the loop and A, B, C (0 <= A, B, C < 2 k) are the parameters of the loop.

The input is finished by a line containing four zeros.

Output

The output consists of several lines corresponding to the instances on the input. The i-th line contains either the number of executions of the statement in the i-th instance (a single integer number) or the word FOREVER if the loop does not terminate.

Sample Input

3 3 2 16
3 7 2 16
7 3 2 16
3 4 2 16
0 0 0 0

Sample Output

0
2
32766
FOREVER

附上代码:

inline LL extended_euclid(const LL& a, const LL& b, LL &x, LL &y)
{
    if (b == 0)
    {
        x = 1, y = 0;
        return a;
    }
    LL d = extended_euclid(b, a % b, x, y);
    LL x1;
    x1 = x, x = y, y = x1 - a / b * y;
    return d;
}
 
// return ax = b (mod n) 的最小解
inline LL modular_linear_equation_solver(const LL &a, const LL &b, const LL &n)
{
    LL d, x, y;
    d = extended_euclid(a, n, x, y);    // a 与 n 的最大公约数d
    if (b % d)
        return -1;
    LL x0 = x * (b / d) % n + n;    // x0 * d 是 ax = b (mod n) 的一个解
    return x0 % (n / d);            // ax = b (mod n) 的最小解
}
 
int main()
{
#ifndef ONLINE_JUDGE
    freopen("in.txt", "r", stdin);
#endif
    LL A, B, C, k;
    while (~scanf("%lld%lld%lld%lld", &A, &B, &C, &k), A || B || C || k)
    {
        LL d = modular_linear_equation_solver(C, B - A, 1LL << k);
        if (d == -1)
            puts("FOREVER");
        else
            printf("%lld\n", d);
    }
#ifndef ONLINE_JUDGE
    fclose(stdin);
#endif
    return 0;
}



         模板二(解同余方程组)

int ExGcd( int a, int b, int &x, int &y )
{
    if( b == 0 ) { x=1;y=0; return a; }
    int r = ExGcd( b, a%b, x, y );
    int t = x; x = y; y = t - a/b*y;
    return r;
}
int Modline( int r[], int a[], int n )
{
    //  X = r[i] ( mod a[i] )
    int rr = r[0], aa = a[0];
    for(int i = 1; i < n; i++ )
    {
        // aa*x + a[i]*y = ( r[i] - rr );
        int C = r[i] - rr, x, y;
        int d = ExGcd( aa, a[i], x, y );
        if( (C%d) != 0 ) return -1;
        int Mod = a[i]/d;
        x = ( ( x*(C/d)% Mod ) + Mod ) % Mod;
        rr = rr + aa*x; // 余数累加
        aa = aa*a[i]/d; // n = n1*n2*...*nk
     }
    return rr;
}


题目

Biorhythms
Time Limit: 1000MS   Memory Limit: 10000K
Total Submissions: 128268   Accepted: 40676

Description

Some people believe that there are three cycles in a person's life that start the day he or she is born. These three cycles are the physical, emotional, and intellectual cycles, and they have periods of lengths 23, 28, and 33 days, respectively. There is one peak in each period of a cycle. At the peak of a cycle, a person performs at his or her best in the corresponding field (physical, emotional or mental). For example, if it is the mental curve, thought processes will be sharper and concentration will be easier.
Since the three cycles have different periods, the peaks of the three cycles generally occur at different times. We would like to determine when a triple peak occurs (the peaks of all three cycles occur in the same day) for any person. For each cycle, you will be given the number of days from the beginning of the current year at which one of its peaks (not necessarily the first) occurs. You will also be given a date expressed as the number of days from the beginning of the current year. You task is to determine the number of days from the given date to the next triple peak. The given date is not counted. For example, if the given date is 10 and the next triple peak occurs on day 12, the answer is 2, not 3. If a triple peak occurs on the given date, you should give the number of days to the next occurrence of a triple peak.

Input

You will be given a number of cases. The input for each case consists of one line of four integers p, e, i, and d. The values p, e, and i are the number of days from the beginning of the current year at which the physical, emotional, and intellectual cycles peak, respectively. The value d is the given date and may be smaller than any of p, e, or i. All values are non-negative and at most 365, and you may assume that a triple peak will occur within 21252 days of the given date. The end of input is indicated by a line in which p = e = i = d = -1.

Output

For each test case, print the case number followed by a message indicating the number of days to the next triple peak, in the form:

Case 1: the next triple peak occurs in 1234 days.

Use the plural form ``days'' even if the answer is 1.

Sample Input

0 0 0 0
0 0 0 100
5 20 34 325
4 5 6 7
283 102 23 320
203 301 203 40
-1 -1 -1 -1

Sample Output

Case 1: the next triple peak occurs in 21252 days.
Case 2: the next triple peak occurs in 21152 days.
Case 3: the next triple peak occurs in 19575 days.
Case 4: the next triple peak occurs in 16994 days.
Case 5: the next triple peak occurs in 8910 days.
Case 6: the next triple peak occurs in 10789 days.

#include

int ExGcd( int a, int b, int &x, int &y )
{
    if( b == 0 ) { x=1;y=0; return a; }
    int r = ExGcd( b, a%b, x, y );
    int t = x; x = y; y = t - a/b*y;
    return r;
}
int Modline( int r[], int a[], int n )
{
    //  X = r[i] ( mod a[i] )
    int rr = r[0], aa = a[0];
    for(int i = 1; i < n; i++ )
    {
        // aa*x + a[i]*y = ( r[i] - rr );
        int C = r[i] - rr, x, y;
        int d = ExGcd( aa, a[i], x, y );
        if( (C%d) != 0 ) return -1;
        int Mod = a[i]/d;
        x = ( ( x*(C/d)% Mod ) + Mod ) % Mod;
        rr = rr + aa*x; // 余数累加
        aa = aa*a[i]/d; // n = n1*n2*...*nk
     }
    return rr;
}
// test
int main()
{
    int a1, b, c, d, flag = 1;
    int r[10], a[10];
    while( scanf("%d%d%d%d", &a1, &b, &c,&d))
    {
        if(a1 == -1 && b == -1 && c == -1 && d == -1)
            break;
        r[0] = a1;
        r[1] = b;
        r[2] = c;
        a[0] = 23;
        a[1] = 28;
        a[2] = 33;
        int m = Modline( r, a, 3 ) % 21252;
        if(m <= d)
            printf("Case %d: the next triple peak occurs in %d days.\n", flag++, (m + 21252 - d));
        else {
            printf("Case %d: the next triple peak occurs in %d days.\n", flag++, m % 21252 - d);
        }
    }
    return 0;
}






你可能感兴趣的:(ACM_数学)