【PAT】2. 數學問題
【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;//分子,分母
};
對分數的這種表示指定三項規則:
- 使down為非負數。如果分數為負,那麼令分子up為負即可
- 如果該分數恰為0,那麼規定其分子為0,分母為1
- 分子和分母沒有除了1以外的公約數
分數的化簡:
- 如果分母down為負數,那麼令分子up和分母down都變為相反數(使down為非負數)
- 如果分子up為0,那麼令分母down為1
- 約分:求出分子絕對值與分母絕對值的最大公約數d,然後令分子分母同時除以d
分數的輸出:
- 輸出分數前要對其化簡
- 如果分數r的分母down為1,說明是整數,直接輸出分子
- 如果分數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~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++; }
- 如果上面步驟結束後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=Cn−1m+Cn−1m−1,遞迴終點為 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) } } }