# <center>輾轉相除法 **輾轉相除法是一種尋找兩數最大公因數的方法,也叫做歐幾里得演算法。以下是它的基本步驟:** **給定兩個"正"整數a和b,並讓a >= b。 將a除以b取得商數和餘數 (a = bq + r)。 如果餘數r等於0,那麼b就是a和b的最大公因數。 如果餘數r不等於0,那麼讓a = b,b = r,並回到第二步繼續運算,直到找到最大公因數。** ## <center>證明 **假設d1 = (a , b),d2 = (b , r),求證d1 = d2。** **因為d1是a和b的最大公因數,因此 d1|b,d1|a。("|"為整除的意思)** **因此存在整数1和q滿足a = bq + r,得 r=a−bq=a×1−b×q 且 r為d1之倍數** **也就是d1|(b,r)。所以d1|d2。** **因為d2是b和r的最大公因數,所以 d2|b,d2|r。 因此同樣存在整數1和q。得 a=bq+r=b×q+r×1 所以d2|a(q為整數,且b跟a為d2之倍數), 也就是d2|(a,b),所以d2|d1。 而 d1|d2 且 d2|d1 ,在d1和d2都大於等於1(最大公因數),因此d1 = d2, 即(a , b) = (b , r) 得證。** ## **題目練習** **https://zerojudge.tw/ShowProblem?problemid=a024** ## **參考程式碼** ```cpp= #include<bits/stdc++.h> #define endl '\n' #define IO cin.tie(0) -> sync_with_stdio(0) using namespace std; int gcd(int a , int b){ int times = a/b; int r = a - b * times; if(r == 0) return b; else return gcd(b , r); } int main(){ int a , b; while(cin >> a >> b){ cout << gcd(a , b) << endl; } } ``` # <CENTER>LCS最長共同子序列 **最長共同子序列定義如下:如有兩序列 S1 、 S2 ,兩序列各自可分離出若干個子序列。若 S1 存在一個子序列 S1 並且 S2 存在一個子序列 S2 完全相等於 S1 ,則稱該子序列為兩序列 S1 、 S2 的共同子序列。** **子序列定義如下:在不違反原序列的順序之下,挑出任意個元素重新形成的有序元素列。** **題目:** **https://dandanjudge.fdhs.tyc.edu.tw/ShowProblem?problemid=a310** ```cpp= #include<bits/stdc++.h> #define endl '\n' #define IO cin.tie(0) -> sync_with_stdio(0) using namespace std; typedef long long ll; signed main(){ IO; string s1 , s2; while(cin >> s1 >> s2){ s1 = "*" + s1 ; s2 = "*" + s2; int SCROLL[2][s2.size()]; memset(SCROLL , 0 , sizeof(SCROLL)); for(int i = 1 ; i < s1.size() ; i++){ for(int j = 1 ; j < s2.size() ; j++){ if (s1[i] == s2[j]) SCROLL[i % 2][j] = SCROLL[(i - 1) % 2][j - 1] + 1; else SCROLL[i % 2][j] = max(SCROLL[(i - 1) % 2][j], SCROLL[i % 2][j - 1]); } } cout << SCROLL[(s1.size() - 1) % 2][s2.size()-1] << endl; } return 0; } ``` # <center> 背包問題 **一種求最佳解的方式,像是有一個背包(容量有限),問裝哪些東西能獲得的目標值最多?** ## 01背包 每項物品都只有一個 背包容量為`M`,有`N`個物品。 $v[i] \implies 第i個物品的價值$ $w[i] \implies 第i個物品的重量$ **https://dandanjudge.fdhs.tyc.edu.tw/ShowProblem?problemid=a279** ```cpp= #include<bits/stdc++.h> #define endl '\n' using namespace std; int main(){ int n , c; while(cin >> n >> c){ // n 個數 ; c 容量 int dp[c+1]; int a[n] , b[n];//a價值 b體積 memset( dp, 0, sizeof(dp)); // int x[] //memset(x, 0, sizeof(x)) memset(a,0,sizeof(a)); memset(b,0,sizeof(b)); for(int i = 0 ; i < n ; i++){ cin >> a[i] >> b[i]; } for(int i = 0 ; i < n ; i++){ for(int j = c ; j >= b[i] ; j--){ dp[j] = max(dp[j] , a[i] + dp[j-b[i]]); } } cout << dp[c] << endl; } } ``` ## 有限背包 #### 一開始的想法 把每一個物品都做一次`O/1背包`。 ```cpp= vector<int> v(N), w(N), k(N); vector<int> dp(M + 1, 0); for(int i = 0; i < N;i ++) for(int t = 0; t < k[i]; t++) for(int j = M; j >= w[i]; j--) dp[j] = max(dp[j], v[i] + dp[j - w[i]]); ``` 時間複雜度$O(kMN)$ 空間複雜度$O(M)$ #### 嘗試優化 把物品的數量拆成二的倍數。 ```cpp= vector<int> v(N), w(N), k(N); for(int i = 0; i < N; i++) { int rest = k[i]; int weight, val; for(int t = 1; t <= rest; t <<= 1) { //t *= 2 rest -= t; weight = t * w[i], val = t * val[i]; for(int j = M;j >= weight; j--) dp[j] = max(dp[j], val + dp[j - weight]); } weight = t * w[i], val = t * v[i]; for(int j = M; j >=; j++) { dp[j] = max(dp[j], val + dp[j - weight]); } } ``` 時間複雜度$O(NM\log k)$ 空間複雜度$O(M)$ https://dandanjudge.fdhs.tyc.edu.tw/ShowProblem?problemid=a330 ## 無限背包 從前往後`DP`, 即可。 https://dandanjudge.fdhs.tyc.edu.tw/ShowProblem?problemid=a331 ```cpp= #include<bits/stdc++.h> #define endl '\n' #define mms memset #define ll long long using namespace std; int main(){ ll n , c;//n個 ; c容量 while(cin >> n >> c){ ll av[n];ll bw[n];ll dp[c+1]; mms(av , 0 , sizeof(av)); mms(bw , 0 , sizeof(bw)); mms(dp , 0 , sizeof(dp)); for(int i = 0 ; i < n ; i++){ cin >> av[i] >> bw[i]; } for(int i = 0 ; i < n ; i++){ for(int j = bw[i] ; j <= c ; j++){ dp[j] = max( dp[j] , av[i] + dp[j-bw[i]]); } } cout << dp[c] << endl; } } ```