CPP[3]

簡報:水餃

講師:Roy

還是搞個自我介紹好了

  • 水餃(10)
  • 北資一六(不可能是建電???
  • 失蹤的迎新執秘(不知道但各種失蹤
  • 不會C++所以來教C++
  • 你們都比我電

烤肉好吃

甚麼突然眼前一亮

這是4年前你們學長的學長的學長的學長遇到的困擾

這是前年你們學長的學長的學長的簡報

這是今年你們學長姊的簡報

明年的學術,你知道該怎麼做了吧

所以學長做了甚麼

  • 整個陣列從左掃到右
  • 每次挑兩個數字 a, b出來,看誰比較大
  • 如果 a > b, swap(a, b)
  • 有甚麼問題
    • 每次run完整個陣列一定會把最大/最小值搬到最右邊

    • 但不一定把最小/最大值搬到最左邊

  • 需要兩個迴圈
  • bubble sort
3 5 2 1 6
3 2 5 1 6
3 2 1 5 6
2 3 1 5 6
2 1 3 5 6
1 2 3 5 6
for(int i = 0; i < n; i++) {
    for(int j = 0; j < n - i; j++) {
        if(arr[j] >= arr[j + 1]) {
            swap(arr[j], arr[j + 1]);
        }
    }
}

好麻煩

O(N²)

有沒有快一點的

std::sort

而且他是O(N log N)

  • from、to 都是 iterator,範圍是 [from, to)
  • function 為比較的方式,分為下面三種,預設為升序
  • greater <int>() 降序排列
  • 自己刻出的函式名 自訂排序方式

sort(from, to, function)

vector <int> vec;
for (int i = 2; i <= 10; i += 2) vec.push_back(i);
for (int i = 1; i <= 10; i += 2) vec.push_back(i);

sort(vec.begin(), vec.end()); //預設升序
// {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
sort(vec.begin(), vec.end(), greater <int>())
// {10, 9, 8, 7, 6, 5, 4, 3, 2, 1}
int arr[10] = {2, 4, 6, 8, 10, 1, 3, 5, 7, 9};
sort(arr, arr+10, greater <int>());

O(N log N)

  • 自己寫 function
  • 接收兩個相同類型的參數,回傳 bool
  • 回傳 true,表示第一個參數排在第二個參數前
  • 回傳 false,則表示排在後

自訂排序方式

bool cmp(int a, int b) {
    return a > b;
}
vector <int> vec;
for (int i = 2; i <= 10; i += 2) vec.push_back(i);
for (int i = 1; i <= 10; i += 2) vec.push_back(i);

sort(vec.begin(), vec.end(), cmp);
// {10, 9, 8, 7, 6, 5, 4, 3, 2, 1}

枚舉

枚舉

  • 把所有情況列出來
  • 舉個🌰
  • 0 ~ 2 子集合
  • {∅}, {0}, {1}, {2}, {0, 1}, {1, 2}, {0, 2}, {0, 1, 2}
  • 對於每個元素,有選或不選兩種情況
  • 那要怎麼實作

遞迴

{}

{}

{0, 1}

{}

{2}

{1}

{1, 2}

{0}

{0, 2}

{0, 1}

{0, 1, 2}

{0}

{1}

{}

{0}

  • 0要不要選
  • 1要不要選
  • 2要不要選

遞迴

#include<bits/stdc++.h>
using namespace std;

vector<int> subset;
int n = 3;
void search(int k) {
    if (k == n) {
    	for (int i = 0; i < subset.size(); i++) 
        	cout << subset[i] << " "; 
        cout << endl;
        return;
    } else {
        // 不選擇第 k 個元素
        search(k + 1);

        // 選擇第 k 個元素
        subset.push_back(k);
        search(k + 1);

        // 移除最後一個元素
        subset.pop_back();
    }
}
int main() {
    search(0);
}

位元運算

用 0 或 1 來表示有沒有選

選的情況可以表示成一個二進制數字

數字 二進制數字 子集合
0 000
1 001 0
2 010 1
3 011 0, 1
4 100 2
5 101 0, 2
6 110 1, 2
7 111 0, 1, 2

位元運算

#include<bits/stdc++.h>
using namespace std;

int main() {
    int n = 3;
    for (int s = 0; s < (1 << n); s++) {
	vector<int> subset;
    
        for (int i = 0; i < n; i++) 
       		if (s & (1 << i)) subset.push_back(i);
            
       	for (int i = 0; i < subset.size(); i++) 
       	    cout << subset[i] << " ";
       	cout << endl;
    }
}

再舉個🌰

剛剛那個是找子集合

顯然也可以要你找有幾種排列方式

0 1 2
0 2 1
1 0 2
1 2 0
2 0 1
2 1 0

 

又是遞迴

{}

{0}

{2, 1}

{2, 0}

{0, 2}

{0, 1}

{2}

{1}

{1, 2}

{1,0}

{0, 1, 2}

{0, 2, 1}

{1, 0, 2}

{1, 2, 0}

{2, 0, 1}

{2, 1, 0}

又是遞迴

#include<bits/stdc++.h>
using namespace std;

int n = 3;
vector<int> permutation;//排列結果
vector<bool> chosen(n);//是否選擇

void search() {
	if (permutation.size() == n) {
    	for (int i = 0; i < n; i++)
    	    cout << permutation[i] << " ";
    	cout << endl;
    } else {
    	for (int i = 0; i < n; i++) {
        	if (chosen[i]) continue;
            chosen[i] = true;
            permutation.push_back(i);
            search();
            chosen[i] = false;
            permutation.pop_back();
        }
    }
}
int main() {
    search();
}

STL next_permutation

#include<bits/stdc++.h>
using namespace std;

int main() {
    int n = 3;
    vector<int> permutation;
    for (int i = 0; i < n; i++) {
    	permutation.push_back(i);
    }
    
    do {
        for (int i = 0; i < n; i++) 
        	cout << permutation[i] << " ";
    	cout << endl;
    } while (next_permutation(permutation.begin(),permutation.end()));
}

貪心

貪心

  • 貪婪
  • 字面上意思(?
  • 好東西看到就拿
  • 每次都挑選對當前來說最好的選擇
    • 一條數列 有正有負
    • 可以拿走部分的數字
    • 希望拿到的數字的總和最大
    • 不拿負的就好了
  • 額有些可能不一定是對的
  • 通靈(?

貪心🌰

  • 有10元, 5元, 1元的硬幣
  • n 元至少需要多少硬幣
  • 先從大的開始拿
int coins[] = {10, 5, 1};
int n = 28, cnt = 0;
for (int i = 0; i < 3; i++) {
    cnt += n / coins[i];
    n %= coins[i];
}
cout << cnt; 

但不見得類似的都能這樣寫

例如 {1, 3, 4} 湊 6 就會發現燒雞了

怎麼辦 dp 後面會講

貪心🌰

先試試看

餐點吃完所需要的時間 e 比較長先吃

因為他邊吃的同時,老闆還能繼續煮別人的

越早開始吃,吃飯時間和別人的烹煮時間可以重疊進行

#include <bits/stdc++.h>
using namespace std;
#define int long long

signed main() {
    int n;
    while (cin >> n) {
        if (!n) break;
        vector<pair<int, int>> v;
        
        for (int i = 0; i < n; i++) {
            int c, e;
            cin >> c >> e;
            v.push_back({e, c});
        }

         sort(v.begin(), v.end(), greater<pair<int, int>>());  // pair 的 greater 會先比較第一項大小再比較第二項
        

        int curTime = 0, maxTime = 0;
        for (int i = 0; i < n; i++) {
            curTime += v[i].second;
            maxTime = max(maxTime, curTime + v[i].first);
        }
        cout << maxTime << endl;
    }
    return 0;
}

貪心🌰

  • 先從最小能湊的數開始考慮
  • 假設已經能湊出從 1 到某個數 k 之間的所有數
  • 如果下一個硬幣值比 k 還大,就湊不出來
  • 反之你可以把這個數加進去,把可湊範圍往後延伸
#include <bits/stdc++.h>
using namespace std;
#define int long long

signed main() {
    int n;
    cin >> n;
    vector<long long> coins(n);
    for (int i = 0; i < n; i++) cin >> coins[i];
    
    sort(coins.begin(), coins.end());

    int smallest = 1; //目前可湊 [1, smallest)
    for (int i = 0; i < n; i++) {
        if (coins[i] > smallest) break; //欸湊不到
        smallest += coins[i];
    }

    cout << smallest << "\n";
}

反悔貪婪

先做再說,後果等等處理

給你一個數列,可以從左至右取數字,問在

過程中數字和不小於零的情況下,最多可以

拿取幾個數字?

作法

4 -4 1 -3 1 -3

總和:0

先把所有數字都加

發現總和 < 0 的時候放棄最小的數字

作法

4 -4 1 -3 1 -3

總和:1

先把所有數字都加

發現總和 < 0 的時候放棄最小的數字

作法

4 -4 1 -3 1 -3

總和:-2

先把所有數字都加

發現總和 < 0 的時候放棄最小的數字

作法

4 -4 1 -3 1 -3

總和:2

先把所有數字都加

發現總和 < 0 的時候放棄最小的數字

作法

4 -4 1 -3 1 -3

總和:3

先把所有數字都加

發現總和 < 0 的時候放棄最小的數字

作法

4 -4 1 -3 1 -3

總和:0

先把所有數字都加

發現總和 < 0 的時候放棄最小的數字

ans:5

#include<bits/stdc++.h>
using namespace std;

int main(){
    priority_queue<long long,vector<long long>,greater<long long>> pq;
    //pq維護代價最大的藥水
    int n;
    cin >> n;
    long long hp = 0, cnt = 0;
    while(n--) {
        int k;
        cin >> k;
        hp += k;
        cnt++;
        pq.push(k);
        while(hp < 0){//生命少於 0 開始反悔
            cnt--;
            hp -= pq.top();
            pq.pop();
        }
    }
    cout << cnt << endl;
}

code

簡報做不玩了這個當作業吧

dp[0]

dp是甚麼

dynamic programming

動態規劃

簡單來說

把大問題拆成小問題

然後把小問題的答案記下來,避免重複計算

好抽象 來點飯粒

前綴和

每次選字都是前墜河

前墜河

一個陣列所有前方的元素和

1 5 2 4 3
1 6 8 12 15

原陣列

前綴和

如果每個元素都要重新加一遍

加法次數

1

2

3

4

5

第 i 個元素需要 i + 1 次

O(N²)

for (int i = 0; i < n; i++) 
	for (int j = 0; j <= i; j++) 
        pre[i]+=a[j];
	

dp 轉移式

#include<bits/stdc++.h>
using namespace std;

signed main() {
	int n;
	cin >> n;
	int a[n],pre[n];
	for (int i = 0; i < n; i++) cin >> a[i];
	pre[0] = a[0];
	for (int i = 1; i < n; i++) pre[i] = pre[i - 1] + a[i];
	for (int i = 0; i < n; i++) cout << pre[i] << " ";
}

  • 前面都加過了就不要再加一遍了
  • 第 i 個元素前綴和 = 第 i - 1 個元素前綴和 + 第 i 個數
  • O(N)
#include<bits/stdc++.h>
using namespace std;

signed main() {
	int n;
	cin >> n;
	int a[n];
	for (int i = 0; i < n; i++) cin >> a[i];
	for (int i = 1; i < n; i++) a[i] += a[i - 1];
	for (int i = 0; i < n; i++) cout << a[i] << " ";
}// 其實不用多開一個

區間和

#include<bits/stdc++.h>
using namespace std;

signed main(){
	int n, l, r;
	cin >> n >> l >> r;
	vector<int> v(n);
	
	for (int i = 0; i < n; i++) cin >> v[i];
	for (int i = 0; i < n; i++) v[i] += v[i - 1];//前綴和

	cout << v[r] - (l?v[l-1]:0) << "\n";
}

  • (l, r) 區間和
  • pre[r] - pre[l - 1] (l > 0)

費氏數列

廢事樹列

之前遞迴講到的東東

幹嘛不用遞迴就好

  • 每次計算 fibo(n) 都會重複計算 fibo(n-1) 和 fibo(n-2)

  • O(2ⁿ)

#include <bits/stdc++.h>
using namespace std;

vector<int> dp(30);

int main() {
    dp[0] = 0;
    dp[1] = 1;
    
    for (int i = 2; i < dp.size(); i++) {
        dp[i] = dp[i - 1] + dp[i - 2];
    }
    
    int q;
    while (cin >> q) {
        cout << dp[q] << '\n';
    }
}

前面貪心講到的錢錢問題

喔他其實是背包之後才講

反正我先把 AC 扣丟這

#include <iostream>
#include <vector>
using namespace std;
const int INF = 1e9;

int main() {
    int n, x;
    cin >> n >> x;

    vector<int> c(n); // 每種硬幣面額
    for (int j = 0; j < n; ++j) {
        cin >> c[j];
    }

    vector<int> amount(x + 1); // amount[i] 表示湊出金額 i 需要的最少硬幣數
    amount[0] = 0;

    // 計算每個金額所需的最少硬幣數
    for (int i = 1; i <= x; ++i) {
        amount[i] = INF; // 預設
        for (int j = 0; j < n; ++j) {
            if (i - c[j] >= 0) { 
                // 可以用這個硬幣湊
                amount[i] = min(amount[i], amount[i - c[j]] + 1);
                // 用這個硬幣 + 之前湊出 (i - c[j]) 的最少硬幣
            }
        }
    }
    int answer = amount[x];
    if (answer == INF) answer = -1;
    cout << answer << "\n";
}
Made with Slides.com