Greedy 貪婪

Yeedrag owo

 

甚麼是貪婪?

 

甚麼是貪婪?

 

例子: 新台幣有1,5,10,100元,如果我想要湊出總價為\(k\)的值,最少要幾個硬幣?

 

當然是優先換大的幣值R

甚麼是貪婪?

 

A:在每一步採用當前看起來最好的選擇,進而希望使最終答案最好的方法

使用前要想想,這個題目貪婪確定會是對的嗎?

現實中貪婪演算法的例子

貪婪的吃雞塊直到癱瘓,得到人類吃雞塊上界

現實中貪婪演算法的例子

貪婪的吃便當,直到吃飽為止

你們學過的貪婪例子

BubbleSort泡沫排序:

每次遍歷攤貪婪的把大的換上去

SelectionSort選擇排序:

每次遍歷貪婪的找出最大值,並且放到最後

Priority Queue:

每次插入時,大的往前面走

怎麼想出一個

好的貪婪方式?

數學歸納法

 

通靈(?

實際練習owo

(上禮拜的PQ困難題)

 

貪婪做法:

每次把目前最小的兩個加起來

(不嚴謹的)說明:

假設有四個數字\(a,b,c,d\),且\(a<b<c<d\)

和\(c<d<(a+b)\),則:

照順序合併下去:

\(sum1 = (a+b)+((a+b)+c)+((a+b)+c+d)\)

每次選最小的合併:

\(sum2 = (a+b)+(c+d)+((a+b)+c+d)\)

\(sum1<sum2\) 發現每次取最小會比任何沒取最小好

另外想法:觀察後發現後面加值進總和時,加進去的值會和前面合併的有關,故希望前面合併越小越好

用pq實作的扣的:

#include<iostream>
#include<queue>
#include<vector>
using namespace std;
int main(){
    ios_base::sync_with_stdio(false); cin.tie(0);
    int n;
    while(cin>>n&&n!=0){
        priority_queue<int,vector<int>,greater<int>> pq;
        for(int i=0;i<n;i++){
            int tmp;
            cin>>tmp;
            pq.push(tmp);
        }
        long long sum = 0;
        while(pq.size()!=1){
            int a = pq.top();
            pq.pop();
            int b = pq.top();
            pq.pop();
            pq.push(a+b);
            sum+=(a+b);
        }
        cout<<sum<<endl;

    }
}

上禮拜TOI潛力組的第一題owo

怎麼做?

分情況!

1.目前的種類和前個最新種類不一樣:

把最新的種類改成目前種類

把總和加上去

2.目前的種類和前個最新種類一樣:

比較前一個值和新的哪個比較大,大的換上去

扣的:

#include<iostream>
#include<queue>
#include<vector>
using namespace std;
int main(){
    ios_base::sync_with_stdio(false); cin.tie(0);
    int n,k;
    cin>>n>>k;
    int cur = 0;//紀錄目前拿取最新的種類
    int curnum = 0;//紀錄目前拿取最新的價值
    int yum[n];//美味度
    int type[n];//種類
    for(int i=0;i<n;i++){
        cin>>yum[i];//輸入價值
    }
    for(int i=0;i<n;i++){
        cin>>type[i];//輸入種類
    }
    long long int sum = 0;
    for(int i=0;i<n;i++){
        if(cur==type[i]){
            if(yum[i]>curnum){
            	//比較同種類,誰的價值大
                sum+=(yum[i]-curnum);
                curnum = yum[i];
            }
        } else {
        	//第一種情況,直接貪婪拿取
            sum += yum[i];//價值加進總和
            cur = type[i];//更新種類
            curnum = yum[i];//更新價值
        }
    }
    cout<<sum<<endl;
}

自己試試看想一題吧~

有四種情況:

吃最慢的先

吃最快的先

做最慢的先

做最快的先

哪一個比較好?

#include<iostream>
#include<algorithm>
#include<queue>
#include<climits>
#include<vector>
using namespace std;
bool cmp(pair<int,int> a,pair<int,int> b){
    return a.second>b.second;//吃得慢的放前面
}
int main(){
    ios_base::sync_with_stdio(false); cin.tie(0);
    int n;
    while(cin>>n&&n){
        vector<pair<int,int>> vec;
        for(int i=0;i<n;i++){
            int a,b;
            cin>>a>>b;
            vec.push_back(make_pair(a,b));
        }
        long long int ans = LLONG_MIN;//include<climits>
        long long int sum = 0;
        sort(vec.begin(),vec.end(),cmp);
        for(int i=0;i<n;i++){
            sum+=vec[i].first;
            ans = max(ans,(long long)sum+vec[i].second);
        }
        cout<<ans<<endl;
    }
}

Code:

反悔貪婪

先做再說,後果之後再看ㄅ

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

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

拿取幾個數字?

做法:

先遇到數字就放進總和,

如果總和小於零了,就把有參與目前總和的

值中代價最大(扣最多)的持續減掉直到總和>=0

       4       -4        1       -3        1       -3

總和:

4

0

1

-2 (+4) = 2

3

0

A:5個

扣的

#include<iostream>
#include<algorithm>
#include<vector>
#include<queue>
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;
    long long 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;
}

雙指針

給你兩個數列,求兩個數列中

各取一數最小的差是多少?

直觀想法:

每個數字都掃一遍數列找最小

#include<iostream>
#include<algorithm>
#include<cstdlib>
#include<vector>
using namespace std;
int main(){
    ios_base::sync_with_stdio(false); cin.tie(0);
    int n,m;
    cin>>n>>m;
    int array1[n];
    int array2[m];
    for(int i=0;i<n;i++){
        cin>>array1[i];
    }
    for(int i=0;i<m;i++){
        cin>>array2[i];
    }
    int ans = 2147483647;
    for(int i=0;i<n;i++){
        for(int j=0;j<m;j++){
            ans = min(ans,abs(array1[i]-array2[j]));
        }
    }
    cout<<ans<<endl;
}

複雜度:

\(O(NM)\)

TLE....

雙指針!

   2    3    5
  -1    7    8

Array1

Array2

最小差:

3

2

每次把目前指針比較小的往上一格,

直到其中一個指針到最後。

扣的:

 

#include<iostream>
#include<algorithm>
#include<cstdlib>
#include<vector>
using namespace std;
int main(){
    ios_base::sync_with_stdio(false); cin.tie(0);
    int n,m;
    cin>>n>>m;
    int array1[n];
    int array2[m];
    for(int i=0;i<n;i++){
        cin>>array1[i];
    }
    for(int i=0;i<m;i++){
        cin>>array2[i];
    }
    sort(array1,array1+n);
    sort(array2,array2+n);//要先sort過才能雙指針
    int p1 = 0; int p2 = 0;//兩個指針
    int ans = 2147483647;
    while(p1<n&&p2<m){
        ans = min(ans,abs(array1[p1]-array2[p2]));
        if(array1[p1]<array2[p2]){
            p1++;//指到小的指針往上一格
        } else {
            p2++;
        }
    }
    cout<<ans<<endl;
}

時間複雜度:

\(O(N+M)\)

好耶AC

無情ㄉ題單

 

都比上課例題簡單很多 應該沒問題ㄅ owo

想要變態題單可以來要ㄛㄛ

Made with Slides.com