進位制轉換
一個 \(m\) 進位制的數字 \(\overline{a_0a_1\cdots a_k}\) 實際上是 \(a_0m^k+a_1m^{k-1}+\cdots+a_k\)。
\(10\) 進位制轉 \(m\) 進位制:每次除以 \(m\) 並取出餘數。
\(m\) 進位制轉 \(10\) 進位制:計算 \(a_0m^k+a_1m^{k-}+\cdots+a_k\)。
進位制轉換
問題簡述:將 \(n\) 進位制數轉換成 \(m\) 進位制數
思路:先轉換成 \(10\) 進位制,再轉換成 \(m\) 進位制
std:
#include <iostream>
#include <string>
using namespace std;
int n; //轉化前為n進位制
int m; //轉化後為m進位制
int num_10 = 0; //轉化成的10進位制
string num_n; //轉化前的n進位制
string num_m; //轉化後的m進位制
int main(void)
{
cin >> n;
cin >> num_n;
cin >> m;
//n進位制轉為10進位制
int len_n = num_n.length();
for(int i = 0; i < len_n; i++)
{
num_10 *= n;
num_10 += (num_n[i] >= 'A' && num_n[i] <= 'F') ? (num_n[i] - 'A' + 10) : (num_n[i] - '0');
}
while(num_10)
{
num_m = (char)((num_10 % m >= 10) ? (num_10 % m - 10 + 'A') : (num_10 % m + '0')) + num_m;
num_10 /= m;
}
cout << num_m;
return 0;
}
高精度表示
\(\text{int}, \text{long long}\) 分別只能表示 \([−2^31,2^31 ),[−2^63,2^63 )\) 內的數字,超過這個範圍就不能用基礎資料型別直接表示。
我們可以用一個陣列來表示一個高精度數。
例如陣列 \([3,2,1]\) 表示十進位制下的 \(123\) ,\([3,2,5,1,1]\) 表示 \(11523\) 。(相當於是將數翻轉,再拆位)
高精度加減法
與列豎式一樣,從低位向高位依次考慮。
做加法時,如果進位,此位 \(−=10\),更高一位 \(+=1\)。
做減法時,如果借位,此位 \(+=10\),更高一位 \(−=1\)。
程式碼實現:
#include <bits/stdc++.h>
using namespace std;
char a[1005], b[1005];//a,b 兩數
int c[1005], d[1005], e[1005];//c是整數形式的a d是整數>形式的b e是和
int main()
{
cin>>a>>b;
int la = strlen(a);//a的長度
int lb = strlen(b);//b的長度
//轉整數:
for(int i = 1; i <= la; i++)
{
c[i] = a[i-1] - '0';
}
for(int i = 1; i <= lb; i++)
{
d[i] = b[i-1] - '0';
}
//倒序存放:
reverse(c+1, c+la+1);
reverse(d+1, d+lb+1);
int j = la;
if(j < lb) j = lb;//最大長度
//相加並判斷是否進位:
for(int i = 1; i <= j; i++)
{
e[i] += c[i] + d[i];
if(e[i] >= 10)
{
e[i+1]++;
e[i] = e[i] - 10;
}
}
//如果進了一位,說明位數多了一位,j++(位數加一):
if(e[j+1] == 1){
j++;
}
//倒序輸出:
for(int i=j;i>=1;i--){
cout<<e[i];
}
return 0;
}
高精度乘法
還是與列豎式類似,逐個數位相乘,最後化簡。
如果是 \(A\) 的第 \(i\) 位乘以 \(B\) 的第 \(j\) 位,則實際上指的是 \((A[i]×10^i )×(B[j]×10^j )\),貢獻給結果的第 \(i+j\) 位。
複雜度為 \(O(l_A l_B )\)。
程式碼實現:
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
int c[5005],d[5005],e[10010];
char a[5005],b[5005];
int main(){
scanf("%s%s",a,b);
int la=strlen(a),lb=strlen(b);
for(int i=1;i<=la;i++){
c[i]=a[i-1]-'0';
}
for(int i=1;i<=lb;i++){
d[i]=b[i-1]-'0';
}
reverse(c+1,c+la+1);
reverse(d+1,d+lb+1);
for(int i=1;i<=la;i++){
int x=0;
for(int j=1;j<=lb;j++){
e[i+j-1]=c[i]*d[j]+x+e[i+j-1];
x=e[i+j-1]/10;
e[i+j-1]%=10;
}
e[i+lb]=x;
}
int lc=la+lb;
while(e[lc]==0&&lc>1){
lc--;
}
reverse(e+1,e+lc+1);
for(int i=1;i<=lc;i++)cout<<e[i];
return 0;
}
高精度除以低精度
與豎式除法類似,從高位向低位考慮。
豎式除法每次“帶下去”的那個數實際上是目前的餘數,這個餘數在考慮下一位時位權會 \(×10\)。
pair<vector<int>,int> div(vector<int> A, int B){
vector<int> quotient(A.size());
int remainder =0;
for(int i=A.size()-1;i>=0;i--){
quotient[i]=(A[i]+remainder*10)/ B,
remainder=(A[i]+remainder *10)%B;
while(quotient.back()== 0){
quotient.pop_back();
}
}
return {quotient,remainder};
}
高精度壓位
實際上陣列每個位置不僅僅可以裝一個數位。
例如我們可以讓 \([12,34,567]\) 表示 \(7654321\)。
可以限制每個位置裝載 \([0,10^9 )\) 的數字。這樣兩個數字相加不會溢位,但乘法會溢位,需要轉為 \(\text{long long}\) 計算。
階乘之和
問題簡述:
用高精度計算出 \(S = 1! + 2! + 3! + \cdots + n!\)(\(n \le 50\))。
其中 !
表示階乘,定義為 \(n!=n\times (n-1)\times (n-2)\times \cdots \times 1\)。例如,\(5! = 5 \times 4 \times 3 \times 2 \times 1=120\)。
思路:用高精度乘上低精度,每次拿出 \(a_{i-1}\times i\) 即可
std:
#include<iostream>
#include<cstring>
using namespace std;
int n,a[90],b[90],c[90],f[90],d=0,len_a,len_b=1,len_c=1,len_ans,m=1;
string s;
int main(){
cin>>n;
b[0]=1;
for(int i=1;i<=n;i++){
len_a=0;
int p=i;
while(p>0){
a[len_a++]=p%10;
p/=10;
}
for(int j=0;j<len_a;j++)
for(int k=0;k<=len_b;k++)
c[j+k]+=a[j]*b[k];
for(int j=0;j<len_c;j++)
if(c[j]>9) c[j+1]+=c[j]/10,c[j]%=10;
if(c[len_c]) len_c++;
len_ans=len_b,len_b=len_c,m=max(m,len_c);
for(int k=len_c-1;k>=0;k--) b[k]=c[k];
len_c=len_a+len_ans;
memset(c,0,sizeof(c));
for(int j=0;j<m;j++){
f[j]+=b[j];
if(f[j]>9) f[j+1]+=f[j]/10,f[j]%=10;
}
}
while(!f[m]&&m>0) m--;
for(int i=m;i>=0;i--) cout<<f[i];
return 0;
}
組合數學基礎
加法原理:做完一件事有 \(n\) 類方法,每類方法有 \(a_i\) 個方法,那麼做完這件事有 \(a_1+a_2+…+a_n\) 個方法。
乘法原理:做完一件事有 \(n\) 個步驟,每個步驟有 \(a_i\) 個方法,那麼做完這件事有 \(a_1×a_2×…×a_n\) 個方法。
排列數
從 \(n\) 個不同的元素中任取 \(m\) 個元素,按照一定順序排成一列,其方案數稱為 \(A_n^m\)。
第一個位置有 \(n\) 種取法,第二個位置有 \(n−1\) 種取法,第 \(i\) 個位置有 \(n−i+1\) 種取法。
全排列(即 \(m=n\))時,\(A_n^n=n!\)
考慮第 \(n\) 號元素是否選取。
如果不選取,則需要在前 \(n−1\) 個元素中選取 \(m\) 個排成一列,方案數為 \(A_(n−1)^m\)。
如果選取,則首先需要給 \(n\) 號元素指定一個位置(\(m\) 種可能),然後從前 \(n−1\) 個元素中選取 \(m−1\) 個排成一列。
組合數
從 \(n\) 個不同的元素中任取 \(m\) 個元素,組成一個集合,其方案數稱為 \(C_n^m\)。
\(C_n^m\) 與 \(A_n^m\) 的區別在於其不關注選出元素的順序,只關注選取哪些元素。
每一個大小為 \(m\) 的集合都對應著 \(m!\) 個排列。所以 \(A_n^m=m!C_n^m\)。
所以有
組合數 \(C_n^m\) 又常寫作 \(\binom{n}{m}\)。注意 \(n\) 和 \(m\) 上下顛倒了。
同樣考慮第 \(n\) 號元素是否選取。
如果不選取,則需要在前 \(n−1\) 個元素中選取 \(m\) 個組成集合,方案數為 \(C_{n-1}^m\)。
如果選取,則需要在前 \(n−1\) 個元素中選取 \(m−1\) 個組成集合。方案數為 \(C_{n-1}^{m-1}\) 。這裡我們不需要給第 \(n\) 號元素指定位置。
組合數問題
思路:
用遞推式 \(C_n^m=C_{n-1}^m+C_{n-1}^{m-1}\) 計算每個\(C_n^m\mod k\) 的值。
然後使用二維字首和預處理 \(𝑖 ≤ 𝑛,𝑗 ≤ 𝑚\) 的 \(C_i^j\) 有多少個 \(𝑘\) 的倍數。
std:
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<map>
#include<vector>
#include<queue>
#include<set>
#include<unordered_map>
#include<bitset>
#define int long long
using namespace std;
int C[20005][2005],sum[20005][2005];
signed main(){
int t,k;
cin >> t >> k;
for (int i=0;i<=2000;i++){
C[i][0] = 1;
for (int j=1;j<=i;j++)
C[i][j] =(C[i-1][j-1] + C[i-1][j])%k;
}
for (int i=1;i<=2000;i++)
for (int j=1;j<=2000;j++){
sum[i][j] = sum[i-1][j] + sum[i][j-1] - sum[i-1][j-1];
if(C[i][j]==0&&i>j)sum[i][j]++;
}
for (int q=1;q<=t;q++){
int n,m;
cin >> n >> m;
cout<<sum[n][min(n,m)]<<"\n";
}
}
排列數與組合數的性質
這是因為左式相當於從 \(n\) 個元素中選出一個子集,每個元素都有選/不選兩種選擇。
範德蒙德卷積公式:
左式相當於在 \(n+k\) 個元素中選 \(m\) 個,右式相當於列舉在前 \(n\) 個元素中選 \(i\) 個,那麼應當在後 \(k\) 個元素中選 \(m−i\) 個。
一些小問題
\(𝐼\) 每個球都有 \(𝑚\) 種選擇。答案為 \(𝑚^𝑛\) 。
\(𝐼𝐼\) 第一個球有 \(𝑚\) 種選擇,第 \(𝑖\) 個球有 \(𝑚−𝑖+1\) 種選擇,答案為 \(𝐴_𝑚^𝑛\) 。
\(𝑉\) 如果能放下都是一樣的。答案為 \(𝑛≤𝑚\) 。
\(𝑋𝐼\) 答案也是 \(𝑛≤𝑚\) 。
\(𝑉𝐼𝐼𝐼\) 相當於找出 \(𝑛\) 個盒子放下一個球。答案為 \(𝐶_𝑚^𝑛\) 。
\(𝐼𝑋\) 插板法,從 \(𝑛−1\) 個空隙中選擇 \(𝑚−1\) 個板子。答案為 \(C_{n-1}^{m-1}\) 。
\(𝑉𝐼𝐼\) 先憑空變出來 \(𝑚\) 個球,這樣就變成上一個問題了。答案為 \(C_{n+m-1}^{m-1}\) 。
最大公約數與最小公倍數
\(\gcd(𝑎,𝑏)\) 表示 \(𝑎\) 與 \(𝑏\) 的最大公約數,\(\text{lcm}(𝑎,𝑏)\) 表示 \(𝑎\) 與 \(𝑏\) 的最小公倍數。
\(𝑎≥𝑏\) 時 \(\gcd(𝑎,𝑏)=\gcd(𝑎−𝑏,𝑏)\)。
證明:
從而可以推出 \(\gcd(𝑎,𝑏)=\gcd(𝑎 \bmod 𝑏,𝑏)\),可以在 $O(\log \max(a,b)) $ 時間內求出 \(\gcd(𝑎,𝑏)\)。
此稱為歐幾里得演算法。
/*
int gcd(int a, int b){
if(a == 0 && b == 0){
return a + b;
}
if(a >= b){
return gcd(a % b, b);
} else {
return gcd(b % a, a);
}
}*/
int gcd(int a, int b){
return b ? gcd(b, a % b) : a;
}
int lcm(int a, int b){
return a / gcd(a, b) * b;
}
最大公約數和最小公倍數問題
思路:
若 \(𝑥_0 ∤ 𝑦_0\),那麼無解。
考慮某一個質因子 \(𝑝\) ,若 \(𝑓_𝑝 (𝑦_0)\) \(>\) \(𝑓_𝑝 (x_0)\),則有兩種選擇;若\(𝑓_𝑝 (𝑦_0)\) = \(𝑓_𝑝 (x_0)\) ,則有一種選擇。
答案即為\(2^{w(\frac{y_0}{x_0})}\) ,其中\(w(x)\) 表示 \(x\) 的質因子數。對\(𝑦_0 𝑥_0\)進行因數分解即可
std:
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<map>
#include<vector>
#include<queue>
#include<set>
#include<unordered_map>
#include<bitset>
#define int long long
using namespace std;
const int MAXN=100005;
inline int read(){
int x=0,f=1;
char ch=getchar();
while (ch<'0'||ch>'9'){
if (ch=='-') f=-1;
ch=getchar();
}
while (ch>='0'&&ch<='9'){
x=x*10+ch-48;
ch=getchar();
}
return x*f;
}
int gcd(int a,int b){
return b?gcd(b,a%b):a;
}
int lcm(int x,int y){
return x/gcd(x,y)*y;
}
signed main(){
int x,y;
x=read();
y=read();
int ans=0;
if(x==y)ans--;
y*=x;
for(int i=1;i<=sqrt(y);i++){
if(y%i==0&&gcd(i,y/i)==x){
ans+=2;
}
}
cout<<ans;
return 0;
}