1. 程式人生 > 其它 >【PAT】2. 數學問題

【PAT】2. 數學問題

技術標籤:PAT資料結構演算法

【PAT】2. 數學問題

最大公約數與最小公倍數

求解最大公約數

歐幾里得演算法:設a,b均為正整數,則 g c d ( a , b ) = g c d ( b , a % b ) gcd(a,b)=gcd(b,a\%b) gcd(a,b)=gcd(b,a%b),gcd(a,b)來表示a和b的最大公約數

遞迴邊界為gcd(a,0) = a,零和任何一個整數a的最大公約數都是a

int gcd(int a, int b){
    if(b == 0) return a;
    else return gcd(b,a % b);
}

或者

int gcd
(int a, int b){ return !b ? a : gcd(b, a % b); }

求解最小公倍數

一般用lcm(a, b)來表示a和b的最小公倍數,在求得a和b的最大公約數d之後,可以得到a和b的最小公倍數是ab/d,可以藉助集合來理解這個公式。因為a*b可能會溢位,所以一般寫成a/d*b

分數的四則運算

分數的表示

struct Fraction{//分數
    int up, dpwn;//分子,分母
};

對分數的這種表示指定三項規則:

  1. 使down為非負數。如果分數為負,那麼令分子up為負即可
  2. 如果該分數恰為0,那麼規定其分子為0,分母為1
  3. 分子和分母沒有除了1以外的公約數

分數的化簡:

  1. 如果分母down為負數,那麼令分子up和分母down都變為相反數(使down為非負數)
  2. 如果分子up為0,那麼令分母down為1
  3. 約分:求出分子絕對值與分母絕對值的最大公約數d,然後令分子分母同時除以d

分數的輸出:

  1. 輸出分數前要對其化簡
  2. 如果分數r的分母down為1,說明是整數,直接輸出分子
  3. 如果分數r的分子up絕對值大於分母down,說明分數是假分數,應按帶分數的形式輸出,即整數部分為r.up/r.down,分子部分為abs(r.up)%r.down,分母部分為r.down

注意:由於分數的乘法和除法過程中可能使分子或分母超過int型表示範圍,因此一般情況下分子和分母使用long long型儲存

素數

判斷n是否是素數:判定它是否能被 2 , 3 , . . . , ⌊ s q r t ( n ) ⌋ 2,3,...,\left \lfloor sqrt(n) \right \rfloor 2,3,...,sqrt(n)中的一個整除,時間複雜度為 O ( s q r t ( n ) ) O(sqrt(n)) O(sqrt(n))

從1~n進行列舉,判斷每個數是否是素數,就能獲取一個素數表,時間複雜度為 O ( n n ) O(n\sqrt{n}) O(nn )

更高效的埃氏篩法時間複雜度為 O ( n l o g l o g n ) O(nloglogn) O(nloglogn),關鍵在於“篩”。演算法從小到大列舉所有數,對每一個素數,篩取它的所有倍數剩下的就都是素數。

假設從小到大到達某數a時,a沒有被前面的步驟篩去,那麼a一定是素數。因為如果a不是素數,那麼a一定有小於a的素因子,這樣在之前的步驟中a一定會被篩掉。

質因子分解

質因子分解是指將一個正整數n寫成一個或者多個質數的乘積的形式。

每個質因子可以出現不止一次,因此定義結構體factor,存放質因子及其個數

struct factor{
    int x, cnt; //x為質因子,cnt為其個數
}fac[10];

考慮到2*3*5*7*11*13*17*19*23*29已經超過了int範圍,因此對一個int型的數來說,fac陣列大小隻需要開到10就可以了

核心原理:對整數n來說,如果它存在[2,n]範圍內的質因子,要麼這些質因子全部小於等於sqrt(n),要麼只存在一個大於sqrt(n)的質因子,而其餘質因子全部小於等於sqrt(n)。演算法時間複雜度為 O ( n ) O(\sqrt{n}) O(n )

  1. 列舉1~sqrt(n)內的所有質因子p,判斷p是否是n的因子
    • 如果p是因子,給fac陣列中增加質因子p,初始化其個數為0.然後只要p還是n的因子,就讓n不斷除以p,每次操作令p的個數加1,直到p不再是n的因子為止
    • 如果p不是因子,則跳過。
      if(n % prime[i] == 0){
          fac[num].x = n;
          fac[num].cnt = 0;
          while(n % prime[i] == 0){
              fac[num].cnt++;
              n /= prime[i];
          }
          num++;
      }
      
  2. 如果上面步驟結束後n仍然大於1,說明n有且僅有一個大於srqt(n)的質因子(有可能是n本身),這時將這個質因子加入fac陣列,並令其個數為1.
    if(n != 1){
        fac[num].x = n;
        fac[num++].cnt = 1;
    }
    

大整數儲存

按順位儲存:整數的高位儲存在陣列的高位,整數的低位儲存在陣列的低位。如整數235813,有d[0]=3,d[1]=1.d[2]=8,d[3]=5,d[4]=3,d[5]=2。原因是進行運算的時候都是從整數的低位到高位進行列舉。因此注意當把整數按字串%s讀入的時候,實際上是逆位儲存的,即str[0]=‘2’,str[1]=‘3’,…,str[5]=‘3’,因此在讀入後需要在另存至d[]陣列的時候反轉一下。

擴充套件歐幾里得演算法

  • 擴充套件歐幾里得演算法
  • 方程ax+by=c的求解
  • 同餘式ax=c(mod m)的求解
  • 逆元的求解

組合數

求n!中有多少個質因子p

直觀的想法是計算1~n中的每個數各有多少個質因子p,然後將結果累加,時間複雜度為O(nlogn)

由 n!中有 ( n p + n p 2 + n p 3 + ⋯   ) \left ( \frac{n}{p}+\frac{n}{p^2}+\frac{n}{p^3}+\cdots \right ) (pn+p2n+p3n+)個質因子p,可以得到 O ( l o g n ) O(logn) O(logn)的演算法

//計算n!中有多少質因子p
int cal(int n, int p){
    int ans = 0;
    while(n){
        ans += n/p;//累加n/p^k
        n /= p;
    }
    return ans;
}

利用這個演算法可以很快計算出n!的末尾有多少零:由於末尾0的個數等於n!中因子10的個數,這又等於n!中質因子5的個數,只需要帶入cal(n,5)即可求出結果。

n!中質因子p的個數,實際上等於1~n中p的倍數的個數n/p加上(n/p)!中質因子p的個數,遞迴程式碼如下

int cal(int n, int p){
    if(n < p) return 0;
    return n / p + cal(n / p, p);
}

組合數的計算

計算 C n m C^m_n Cnm

通過遞推公式計算: C n m = C n − 1 m + C n − 1 m − 1 C_{n}^{m} = C_{n-1}^{m} +C _{n-1}^{m-1} Cnm=Cn1m+Cn1m1,遞迴終點為 C n 0 = C n n = 1 C_{n}^{0} = C_{n}^{n}=1 Cn0=Cnn=1

long long res[67][67] = {0};
long long C(long long n, long long m){
    if(m = 0|| m = n) return 1;
    if(res[n][m] != 0) return res[n][m];
    return res[n][m] = C(n-1, m) + C(n-1, m-1);
}

通過定義式的變形來計算,時間複雜度為O(m)

long long C(long long n, long long m){
    long long ans = 1;
    for(long long i = 1; i <= m; i++){
        ans = ans * (n - m + i) / i;//注意一定要先乘再除
    }
    return ans;
}

計算 C n m % p C^m_n\%p Cnm%p

  • 遞迴
    int res[1010][1010] = {0};
    int C(int n, int m, int p){
        if(m = 0|| m = n) return 1;
        if(res[n][m]!=0) return res[n][m];
        return res[n][m] = (C(n-1,m) + C(n-1,m-1)) % p;
    }
    
  • 遞推
    void calC(){
        for(int i = 0; i <= n; i++){
            res[i][0] = res[i][i] = 1;//初始化邊界
        }
        for(int i = 2; i <= n; i++){
            for(int j = 0; j <= i/2; j++){
                res[i][j] = (res[i][j-1]+res[i-1][j-1]) % p;
                res[i][i-j] = res[i][j];//C(i,i-j)=C(i,j)
            }
        }
    }