📜  必须为竞争性编程做数学

📅  最后修改于: 2021-04-23 19:19:28             🧑  作者: Mango

çompetitive PAGC(CP)通常不需要知道高级别微积分或一些火箭科学。但是有些概念和技巧在大多数时候是足够的。您绝对可以在没有任何数学背景的情况下开始具有竞争力的编码。但是,随着您深入了解CP的世界,数学变得至关重要。您将遇到的大多数竞争编码问题都有一些数学逻辑或技巧。我们学习的所有算法都是从数学观点出发的。在大多数情况下,数学可以帮助我们在必要的时间限制内解决问题。
所有主题都无法在一篇文章中涵盖,但我们将研究竞争性编码中一些最常见的数学概念。这些概念中的一些乍一看可能看起来太困难了,但是将其应用于问题上会为您带来缓解。
还有一件事是,我只提到了您需要涵盖的内容以及实现此目的的一些技巧,但是您可以借助其他在线资源来学习和实践它们。

1. BigInteger
例如,计算大量的阶乘(假设为100)或接受大量输入,长度约为100000位数。在C++中,即使我们使用long long int,也无法存储这些数字。采取这种数字的一种方法是,更明智地使用vector来将它们放入数组中……每个数字都将包含数组的索引,例如如果数字为12345,则12345%10 = 5将位于index [4]中,并且该数字现在= 12345/10 = 1234。现在1234%10 = 4将在[3]中,依此类推,而1%10 = 1在[0]中,或者也可以使用字符串,这更容易,因为char数组每个索引只允许1个字节,因此您不需要那些调制操作就可以将数字放入索引中。
Java提供了Biginteger类来处理此问题。

2. GCD,LCM,欧几里得算法,扩展欧几里得算法
GCD和LCM的定义是众所周知的(并且在中学任教),我将跳过这些定义。此外,由于lcm(a,b)* gcd(a,b)= a * b,因此计算GCD等效于计算LCM。

现在,我们如何计算两个数字的GCD?
我们当然可以找到两个数的因数,然后确定最高的公因数。但是随着数字的增加(例如155566328819),分解就变得无效了。
这就是Euclid算法得以解决的地方。该算法使用易于证明的事实gcd(a,b)= gcd(b,r),其中r是a除以b或a%b的余数。

int GCD(int A, int B)
{
    if (B == 0)
        return A;
    else
        return GCD(B, A % B);
}

我们可以找到ux + vy = gcd(u,v)这样的数字(x,y)吗?存在着无数对-这就是Bezout的引理。生成这种对的算法称为扩展欧几里得算法。

3. Eratosthenes筛和分段筛
快速生成素数在某些问题中非常重要。让我们开始追逐并介绍Eratosthenes的Sieve。您可以使用Eratosthenes筛子来查找所有小于或等于给定数字N的质数,或者找出数字是否为质数。

Eratosthenes筛网背后的基本思想是,在每次迭代中都会拾取一个质数并消除其所有倍数。消除过程完成后,所有剩余的未标记数字均为质数。假设我们要找到2到50之间的所有质数。从2到50进行迭代。我们从2开始。由于未选中它,所以它是一个质数。现在检查除2以外的所有倍数。现在,我们转到数字3。未选中,因此它是质数。现在检查除3以外的所有3的倍数。现在移至4。我们看到已选中–这是2的倍数!因此4不是素数。我们将继续这样做。

void sieve(int N)
{
    bool isPrime[N + 1];
    for (int i = 0; i& lt; = N; ++i) {
        isPrime[i] = true;
    }
  
    isPrime[0] = false;
    isPrime[1] = false;
  
    for (int i = 2; i * i <= N; ++i) {
  
        // Mark all the multiples of i as composite numbers
        if (isPrime[i] == true) {
            for (int j = i * i; j <= N; j += i)
                isPrime[j] = false;
        }
    }
}

如果数量很大(例如10 ^ 16),该情况下我们需要分段筛。

分段筛的目的是将范围[0..n-1]划分为不同的段,并一一计算所有段中的质数。该算法首先使用“简单筛”来查找小于或等于?(n)的素数。以下是分段筛中使用的步骤。
1.使用简单筛子找到所有素数,直到’n’的平方根,并将这些素数存储在数组“ prime []”中。将找到的素数存储在数组’prime []’中。
2.我们需要在[0..n-1]范围内的所有素数。我们将此范围划分为不同的段,以使每个段的大小最大为?n
3.对每个片段[low..high]进行跟踪
•创建一个数组标记[high-low + 1]。在这里,我们只需要O(x)空间,其中x是给定范围内的元素数。
•遍历在步骤1中找到的所有素数。对于每个素数,在给定范围[low..high]中标记其倍数。
在简单筛中,我们需要O(n)空间,这对于大n可能不可行。在这里,我们需要O(?n)空间,并且一次处理较小的范围

4.模运算,模取幂和模逆
当一个数除以另一个时,取模运算将找到余数。用%符号表示。

例子
假设您有两个数字10和3。10%3为1,因为当10除以3时,余数为1。

特性
1.(a + b)%c =(a%c + b%c)%c
2.(a?b)%c =((a%c)?(b%c))%c
3.(a?b)%c =((a%c)?(b%c)+ c)%c
4.(a / b)%c =((a%c)?(d%c))%c

注意:在上面的最后一个属性中,d是b和c的乘法模逆。

这些属性何时使用?
假设a = 10 ^ 12,b = 10 ^ 12和c = 10 ^ 9 + 7。您必须找到(a?b)%c。将a乘以b时,答案为10 ^ 24,这与标准整数数据类型不符。因此,为避免这种情况,我们使用了属性。 (a?b)%c =((a%c)?(b%c))%c

快速取模
计算O(log b)中的模数m中的a ^ b,
它使用b的二进制扩展,非常简单。

ll expo(ll a, ll b, ll m)
{
    if (b == 0)
        return 1;
    ll p = expo(a, b / 2, m) % m;
    p = (p * p) % m;
  
    return (b % 2 == 0) ? p : (a * p) % m;
}

现在,让我们谈谈模块化逆。

通过使用扩展欧几里得算法,我们可以获得模m的逆。

// Returns modulo inverse of a with respect 
// to m using extended Euclid Algorithm 
// Assumption: a and m are coprimes, i.e., 
// gcd(a, m) = 1 
int modInverse(int a, int m) 
{ 
    int m0 = m; 
    int y = 0, x = 1; 
    
    if (m == 1) 
      return 0; 
    
    while (a > 1) 
    { 
        // q is quotient 
        int q = a / m; 
        int t = m; 
    
        // m is remainder now, process same as 
        // Euclid's algo 
        m = a % m, a = t; 
        t = y; 
    
        // Update y and x 
        y = x - q * y; 
        x = t; 
    } 
    
    // Make x positive 
    if (x < 0) 
       x += m0; 
    
    return x; 
} 

如果gcd(a,p)= 1,则费马小定理给出a ^(p-1)== a(mod p),其中p是素数。因此,我们也可以通过快速求幂来将a的模逆计算为a ^(p-2)。

5.卢卡斯定理

使用卢卡斯定理,我们可以非常快速地以模p(p为素数)计算nCr。卢卡斯定理基本上表明,可以通过将n(i)Cr(i)的结果相乘来计算nCr的值,其中n(i)和r(i)分别是n和r的基数p表示中的单个相同位置的数字。当p小而n,r大时,这是非常有效的。通过使用上面的代码,我们可以预先计算出模p的阶乘和阶乘逆。

6.中国剩余定理<
如果两个数字(正整数)a和b没有相对质数,则它们是相对质数(互质)。如果该集合中的任何两个不同的数字都是相对质数,则数字m1,m2,…. mr是成对的相对质数。中国余数定理说,给定r对的相对质数m1,m2,…. mr,以及任何数b1,b2,b3,..br,我们总能找到一个M留下余数b1,b2,b3 ,..br分别除以m1,m2和…mr。
让我们求解x == r(mod mi),其中mi是成对的互质。
(如果它们不是互质的,则将它们分解为主要力量,如果其中一些相互矛盾,则没有解决方案。)

7.系列和序列
您只需要了解一些基本知识,例如:
•什么是级数,它收敛到某个值吗?
•了解著名的系列,例如三角函数,双曲线等
•如何计算著名序列(例如几何序列,谐波序列)的有限极限
而且序列的基本相同,您只需要了解基础知识即可。
(技巧:使用OEIS网站)
我们有时会遇到这样的情况,即可以将各种编码问题简化为一个数学公式,但经常发现该公式并不是那么简单。我们可以计算初始索引的项,即n = 0、1、2、3,……..然后可以使用OEIS查找数学表达式。

8.加泰罗尼亚语数字
加泰罗尼亚语数字是自然数列,可帮助解决许多计数问题。以n = 0开头的项是:1、1、2、5、14、42、132、429、1430…等等。

基于加泰罗尼亚语数字的问题可能会出现在许多编码竞赛中。因此,深入了解加泰罗尼亚数字始终是一个加分点。
加泰罗尼亚语数字在形成组合问题的封闭解决方案中有广泛的应用。一些示例是:
1.可以使用“ n”个节点形成的二叉搜索树的数量为第n个加泰罗尼亚语编号。
2.通过连接任意2个边可以将n + 2边的凸多边形切成2个或更多三角形的方式的数量为第n个加泰罗尼亚数。
3.对于给定的“ n”对,匹配可能的括号数量的封闭式解决方案是第n个加泰罗尼亚数。

9.鸽洞原理
鸽洞原理是组合数学中使用的强大工具。但是这个想法很简单,可以通过以下特殊问题来解释。想象一下,需要将3羽鸽子放入2个鸽子洞中。能做到吗答案是肯定的,但是有一个陷阱。要注意的是,无论如何放置鸽子,其中一个鸽子洞都必须容纳不止一只鸽子。
逻辑可以推广为更大的数字。信鸽原理指出,如果将n羽以上的鸽子放进n个信鸽的孔中,则某些信鸽必须包含一个以上的信鸽。虽然原理很明显,但其含义令人震惊。

例如,考虑以下语句:“如果从整数1到8中选择五个数字,则其中两个必须加起来等于9。”
说明:每个数字都可以与另一个数字配对,总计为9。总共有四个这样的对:数字1和8、2和7、3和6,最后是4和5。五个数字中的每个数字都属于这四对之一。根据信鸽原则,其中两个数字必须来自同一对,根据构造,这些数字之和为9。

10.包含排除原则

包含排除原则是一个非常基本的计数定理,各种编程竞赛中的许多问题都基于此定理,包含排除原则的正式解释如下:
将A视为对象的集合,| A |作为A中对象的数量,对于B类似,则集合A和B的对象集合的基数(当A和B都不相交时)可以表示为(对于2个有限集合):
| AUB | = | A | + | B |
但是,如果集合不是不相交的,该怎么办?
然后,我们需要在计算A和B的基数时减去两次计数的公共对象,新形式将变为:
| AUB | = | A | + | B | – | A∩B |
这是包含-排除原理的最基本形式。
但是有2套以上,比如说n套。
然后可以这样表示:
(包含=添加,排除=减去)
| A1 U A2 U A3…..U AN | =(包括每个集合的数量,排除成对集合的数量,包括三元组的数量,排除四元组的数量……直到第n个元组被包含(如果是奇数)或被排除(如果是偶数))
即| A1 U A2 U A3…..U AN | =(| A1 | + | A2 | + | A3 | + | A4 |…+ | AN |)–(| A1∩A2 | + | A1∩A3 | + | A1∩A4 | .. +所有组合)+( | A1∩A2∩A3 |…所有组合)………。等等。

该列表并不详尽,但是这些概念在Codeforce,Codechef等竞赛中将非常有用。因此,请握紧笔,纸和笔记本电脑,然后开始练习。

祝您编码愉快!