基礎算法

二分搜

Binary Search

如何在一個陣列中找到是否存在數字 A 

如何在一個陣列中找到是否存在數字 A 

掃一遍    - >   O(n)

如何在一個陣列中找到是否存在數字 A 

掃一遍    - >   O(n)

如果陣列長度  1000000

詢問次數 1000000呢

如何在一個陣列中找到是否存在數字 A 

掃一遍    - >   O(n)

如果陣列長度  1000000

詢問次數 1000000呢

1000000*1000000  -> TLE

如果我對原陣列做一些預處理 ?

如果我對原陣列做一些預處理 ?

sort !

如果我對原陣列做一些預處理 ?

sort !

如果一個陣列是排序過的

如何找到 X 是否存在陣列中?

如果我對原陣列做一些預處理 ?

sort !

如果一個陣列是排序過的

如何找到 X 是否存在陣列中?

二分搜 !!

1  3  5  6  8  12  15  45  50 51

1  3  5  6  8  12  15  45  50 51

詢問 50

我可以找到第一個大於等於50的數字

如果等於50就代表有50

1  3  5  6  8  12  15  45  50 51

詢問 50

1  3  5  6  8  12  15  45  50 51

12 < 50

1  3  5  6  8  12  15  45  50 51

詢問 50

1  3  5  6  8  12  15  45  50 51

1  3  5  6  8  12  15  45  50 51

詢問 50

1  3  5  6  8  12  15  45  50 51

45 < 50

1  3  5  6  8  12  15  45  50 51

詢問 50

1  3  5  6  8  12  15  45  50 51

45 < 50

1  3  5  6  8  12  15  45  50 51

詢問 50

1  3  5  6  8  12  15  45  50 51

50 = 50

找到了 !

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

main(){
    int n,arr[maxn],q;
    cin>>n;
    for(int i=1;i<=n;++i) cin>>arr[i];
    sort(arr+1,arr+1+n);

    while(cin>>q){
        int l = 0,r = n+1,mid;
        if(arr[n] < q){
            cout<<"No"<<endl;
            continue;
        }
        while(l+1<r){
            mid = (l+r)/2;
            if(arr[mid] >= q) r = mid;
            else l = mid;
        }
        if(arr[r] == q) cout<<"yes : "<<r<<endl;
        else cout<<"No"<<endl;
    }
}

二分搜只能拿來找數字 ?

二分搜只能拿來找數字 ?

其實只要有單調性的東西都可以二分搜

換個想法

1  3  5  6  8  12  15  45  50 51

找第一個大於等於45的元素

換個想法

1  3  5  6  8  12  15  45  50 51

找第一個大於等於45的元素

可以把陣列寫成 :

0  0  0  0  0  0  0  1  1  1 

換個想法

1  3  5  6  8  12  15  45  50 51

找第一個大於等於45的元素

可以把陣列寫成 :

0  0  0  0  0  0  0  1  1  1 

0 : < 45

1 : >= 45

那麼第一個大於等於45的

就是第一個 1

那麼第一個大於等於45的

就是第一個 1

0  0  0  0  0  0  0  1  1  1 

那麼第一個大於等於45的

就是第一個 1

0  0  0  0  0  0  0  1  1  1 

可以發現這個陣列也有單調的性質 !!

那麼第一個大於等於45的

就是第一個 1

0  0  0  0  0  0  0  1  1  1 

可以發現這個陣列也有單調的性質 !!

可以用 O(logn) 找到 !!

所以只要東西或結果可以轉換成

0  0  0  0  0  0  0  1  1  1 

像這樣有單調性的01陣列

所以只要東西或結果可以轉換成

0  0  0  0  0  0  0  1  1  1 

像這樣有單調性的01陣列

就可以用二分搜找到第一個1、最後一個0

直接看例題比較快

給你一大堆字串、一值k,

要把他們排成不超過k行

求滿足以下條件時的最小寬度需求

條件 : 

一個字串不能被切斷

每行第一個不為空

 

ex :

k = 4
abcdefg hij kl mn

abcdefg
hij
kl mn

這是這題的其中一解的排法

滿足不超過k行

ex :

k = 4
abcdefg hij kl mn

abcdefg
hij
kl mn

這是這題的其中一解的排法

滿足不超過k行

寬度需求為 8 (abcdefg+'  ')

ex :

4
your  smile  illuminates  like  the  sun... shion...  suki,  -  same

your smile
illuminates like
the sun... shion...
suki, - same

其中一解得排法,寬度需求為20

where is 單調性 ?

where is 單調性 ?

假設把寬度需求從小到大排,

1 為可以排入所有單字

否則為 0

where is 單調性 ?

假設把寬度需求從小到大排,

1 為可以排入所有單字

否則為 0

寬度小

寬度大

00000000000011111111111111

where is 單調性 ?

假設把寬度需求從小到大排,

1 為可以排入所有單字

否則為 0

寬度小

寬度大

00000000000011111111111111

有單調性 !

假設有一台機器,你問他

每行寬度為 T 是否可以排入(合法狀態下)

他會回答 1 or 0

假設有一台機器,你問他

每行寬度為 T 是否可以排入(合法狀態下)

他會回答 1 or 0

就可以跟猜數字一樣,用二分搜找到第一個1

接下來就剩如何做出這台機器

接下來就剩如何做出這台機器

因為已經知道每行的寬度

因此我可以放到這個字放不下,

再換行

接下來就剩如何做出這台機器

因為已經知道每行的寬度

因此我可以放到這個字放不下,

再換行

最後看總行數有沒有大於k

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define maxn 200005
int K,n;
int arr[maxn];
bool chack(int T){
    int cnt = 1,tmp = 0;
    for(int i=1;i<=n;++i){
        if(arr[i] > T) return false;
        if(tmp + arr[i] > T){
            cnt++;
            tmp = arr[i];
            if(cnt > K) return false;
        }
        else tmp += arr[i];
    }
    return cnt <= K;
}
main(){
    ios::sync_with_stdio(0); cin.tie(0);
    cin>>K;
    string s;
    while(cin>>s && !cin.eof()){
        arr[++n] = s.size();
    }
    for(int i=1;i<n;++i) arr[i]++;
    int l = 0,r = 1000006,mid;
    while(l+1<r){
        mid = (l+r)/2;
        if(chack(mid)) r = mid;
        else l = mid;
    }
    cout<<r<<endl;
}

暑訓講過的好題

輸入一個n,問所有n*n的乘法表中,中位數是誰

n <= 1e6

把整張乘法表列出來,再排序找中位數

把整張乘法表列出來,再排序找中位數

1e12 * log(1e12)

TLE

單調性在哪裡 ?

假設我猜答案是k?

單調性在哪裡 ?

假設我猜答案是k?

k小

k大

00000000000011111111111111

1 : 表中 >= k 的數字數量大於等於 (n*n)/2

單調性在哪裡 ?

假設我猜答案是k?

k小

k大

00000000000011111111111111

1 : 表中 >= k 的數字數量大於等於 (n*n)/2

答案為第一個 1

如何計算表中大於等於k的數字數量k

1    2     3    4     5 

2    4     6    8   10

3    6     9   12  15

4    8    12  16  20

5   10   15  20  25

如何計算表中大於等於k的數字數量

1    2     3    4     5 

2    4     6    8   10

3    6     9   12  15

4    8    12  16  20

5   10   15  20  25

第 i 直排 : 1i  2i   3i   4i   5i   6i

k/i 取下高斯就是大於等於k的數字數量

#include<bits/stdc++.h>
using namespace std;
#define int long long
int n;
int cal(int x){
    int sum = 0;
    for(int i=1;i<=n;++i) sum += min(x/i,n);
    return sum;
}
main(){
    cin>>n;
    if(n<=3){
        cout<<n<<endl;
        return 0;
    }
    int l = 1,r = n*n, m, tar = (n*n+1)/2;
    while(l+1<r){
        m = (l+r)/2;
        if(cal(m)>=tar) r = m;
        else l = m;
    }
    cout<<r<<endl;
    return 0;
}

分治

Divide and conquer

什麼是分治 ?

什麼是分治 ?

把東西切一半然後遞迴下去做

什麼是分治 ?

把東西切一半然後遞迴下去做

把問題分成小問題,再把小問題的答案合併

什麼是分治 ?

把東西切一半然後遞迴下去做

把問題分成小問題,再把小問題的答案合併

「在 conquer 時能夠省去維護儲存所有資訊的力氣,只需要專心處理跨分隔線的資訊」,就是分治的精髓

分治步驟 

分治步驟 

分 :  把大問題拆成小問題 

分治步驟 

分 :  把大問題拆成小問題 

邊界 : 問題分到可以直接解決的時候,直接算答案

分治步驟 

分 :  把大問題拆成小問題 

邊界 : 問題分到可以直接解決的時候,直接算答案

治 : 合併小問題的答案,得到大問題的答案

例題1

最大連續和

例題1

最大連續和

給一個包含n個數字的陣列

問一個連續區間的最大和是多少

例題1

最大連續和

給一個包含n個數字的陣列

問一個連續區間的最大和是多少

ex :

3 5 -7 3 2 -8 -9 6 6

 

as : 12

我要寫一個函式 f ( l , r )

目標讓他回傳 l ~ r 的答案 (最大連續和)

如何分,如何治?

如何分?

把陣列切成兩半

如何分?

把陣列切成兩半

如何分?

把陣列切成兩半

答案可能在哪裡 ?

如何分?

把陣列切成兩半

答案可能在哪裡 ?

1 : 全部左邊

如何分?

把陣列切成兩半

答案可能在哪裡 ?

1 : 全部左邊

2 : 全部右邊

如何分?

把陣列切成兩半

答案可能在哪裡 ?

1 : 全部左邊

2 : 全部右邊

3 : 跨越中間

如何治?

如何治?

如果答案全在左邊or右邊

如何治?

如果答案全在左邊or右邊

答案就是 max( f(l,mid), f(mid,r) )

如何治?

如果答案跨越中間 (mid) ?

如何治?

如果答案跨越中間 (mid) ?

跨越中間的答案一定是最大的藍色 + 綠色

綜合三種答案的可能

f (l,r) = 

綜合三種答案的可能

f (l,r) = 

最後邊界情況就是

if  l == r

return arr[l]

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define maxn 200005
#define inf 1e18
int n,arr[maxn];

int f(int l,int r){
    if(l==r) return arr[l];
    int mid = (l+r)/2;
    int res = max(f(l,mid),f(mid+1,r));
    int R = 0,rmax = -inf, L = 0, lmax = -inf;
    for(int i=mid+1;i<=r;++i){
        R += arr[i];
        rmax = max(rmax,R);
    }
    for(int i=mid;i>=l;--i){
        L += arr[i];
        lmax = max(lmax,L);
    }
    res = max(res,lmax+rmax);
    return res;
}
main(){
    cin>>n;
    for(int i=1;i<=n;++i) cin>>arr[i];
    cout<<f(1,n)<<endl;
}

很重要的點就是

你再 f 裡面 cal f 時,要相信他是對的,

這樣他就會是對的

例題 2 : merge sort

merge sort 很適合來理解分治概念

例題 2 : merge sort

merge sort 很適合來理解分治概念

一樣先想像

我要寫出一個函式,他不會回傳東西

但是他會幫我把 l,r 區間排序好

記為 g (l,r)

如何分 ?

如何分 ?

一樣分成兩半

這時我可以呼叫f (l,mid) , f(mid,r)

如何分 ?

一樣分成兩半

這時我可以呼叫f (l,mid) , f(mid,r)

因此現在左右區間都各別排序好了

我要做得只剩下讓整個區間排序好

我要做得只剩下讓整個區間排序好

3   6   9  13

5  7  10  11

3   5   6   7   9  10  11  13

作法就是每次比較兩個陣列的頭 (最小的)

把比較小的丟到新陣列裡,並刪掉

Text

void merge(int arr[], int l, int m, int r) {
    int n1 = m - l + 1, n2 = r - m;
    int L[n1 + 1], R[n2 + 1];
    for (int i = 1; i <= n1; i++) L[i] = arr[l + i - 1];
    for (int i = 1; i <= n2; i++) R[i] = arr[m + i];
    int i = 1, j = 1, k = l;
    while (i <= n1 && j <= n2) arr[k++] = (L[i] <= R[j]) ? L[i++] : R[j++];
    while (i <= n1) arr[k++] = L[i++];
    while (j <= n2) arr[k++] = R[j++];
}
void mergeSort(int arr[], int l, int r) {
    if (l >= r) return;
    int m = l + (r - l) / 2;
    mergeSort(arr, l, m);
    mergeSort(arr, m + 1, r);
    merge(arr, l, m, r);
}
int main() {
    int n; cin>>n;
    int arr[n+1];
    for(int i=1;i<=n;++i) cin>>arr[i];
    mergeSort(arr, 1, n);
    for(int i=1;i<=n;++i) cout<<arr[i]<<' ';
    return 0;
}

例題3 : 逆序數對

給一個長度為 n 的陣列

求數對 (i,j)滿足下條件

1 <= i < j <=n

arr[i] > arr[j]

ex :

2 6 6 1 9 4 3

as = 10

ex :

2 6 6 1 9 4 3

as = 10

一樣設 h(l,r) 會回傳 l~r中的逆序數對

分 : 把陣列分成兩半

分 : 把陣列分成兩半

如果數對中兩個都在左邊或右邊

那我可以直接呼叫 h(l,mid) 、h(mid,r)

再相加得到

因此只要考慮 i,j 在不同邊的數對數

所以對於每個左半邊的 i

要找有幾個右邊的 j

符合  arr[i] > arr[j]

因此只要考慮 i,j 在不同邊的數對數

所以對於每個左半邊的 i

要找有幾個右邊的 j

符合  arr[i] > arr[j]

排序 !

回想剛剛把兩個排序好的陣列合併的過程

回想剛剛把兩個排序好的陣列合併的過程

如果一個左邊的數字現在正被丟出來

(比較到他,然後他比較小)

回想剛剛把兩個排序好的陣列合併的過程

如果一個左邊的數字現在正被丟出來

(比較到他,然後他比較小)

10 12 15

5 8 9

回想剛剛把兩個排序好的陣列合併的過程

如果一個左邊的數字現在正被丟出來

(比較到他,然後他比較小)

10 12 15

 8 9

5

回想剛剛把兩個排序好的陣列合併的過程

如果一個右邊的數字現在正被丟出來

(比較到他,然後他比較小)

10 12 15

 8 9

5

那是不是代表左邊剩下的全部數字都比他大

逆序數對加上左邊剩餘數量

發現這個過程跟merge sort幾乎一模一樣!!

long long merge(int arr[], int l, int m, int r) {
    int n1 = m - l + 1, n2 = r - m;
    int L[n1], R[n2];
    for (int i = 0; i < n1; i++) L[i] = arr[l + i];
    for (int i = 0; i < n2; i++) R[i] = arr[m + 1 + i];
    int i = 0, j = 0, k = l;
    long long inv_count = 0;
    while (i < n1 && j < n2) {
        if (L[i] <= R[j]) {
            arr[k++] = L[i++];
        } else {
            arr[k++] = R[j++];
            inv_count += n1 - i;
        }
    }
    while (i < n1) arr[k++] = L[i++];
    while (j < n2) arr[k++] = R[j++];
    return inv_count;
}
long long mergeSort(int arr[], int l, int r) {
    long long inv_count = 0;
    if (l < r) {
        int m = l + (r - l) / 2;
        inv_count += mergeSort(arr, l, m);
        inv_count += mergeSort(arr, m + 1, r);
        inv_count += merge(arr, l, m, r);
    }
    return inv_count;
}
int main() {
    int n;
    cin >> n;
    int arr[n];
    for (int i = 0; i < n; ++i) cin >> arr[i];
    cout<<mergeSort(arr, 0, n - 1)<<endl;
    return 0;
}

貪心

Greedy

什麼是貪心法?

什麼是貪心?

什麼是貪心?

每次都選對自己最有利的選擇!

什麼是貪心?

每次都選對自己最有利的選擇!

ex :

有 1、5、10元三種面額的硬幣

我要如何使用最少的硬幣來組出 x 元 ?

什麼是貪心?

每次都選對自己最有利的選擇!

ex :

有 1、5、10元三種面額的硬幣

我要如何使用最少的硬幣來組出 x 元 ?

每次都選最還可以選的面額中最大的!

什麼是貪心?

每次都選對自己最有利的選擇!

ex :

有 1、5、10元三種面額的硬幣

我要如何使用最少的硬幣來組出 x 元 ?

每次都選最還可以選的面額中最大的!

28 = 10 + 10 + 5 + 1 + 1 + 1

用了6枚硬幣

一定是對的嗎 ?

一定是對的嗎 ?

取決於有哪些面額,但這個情況下一定對

一定是對的嗎 ?

取決於有哪些面額,但這個情況下一定對

反例 :

有1、5、7三種面額

要組出 11 元

一定是對的嗎 ?

取決於有哪些面額,但這個情況下一定對

反例 :

有1、5、7三種面額

要組出 11 元

greedy策略 : 7+1+1+1+1

最佳解 : 5+5+1

從上面的例子可以發現,

greedy策略在有些情況可以找出最佳解,有些則不行

那我們要如何證明呢?

其實要非常嚴謹的證明是否可以用greedy

是很難的

我們常常會選擇比較簡單的證明

如何證明 ?

我們通常會假設一個不是用greedy策略

找到的最佳解 S (存在某個決策不是greedy)

如何證明 ?

接著我們將 S 的某個不是greedy的決策

換成 greedy

我們通常會假設一個不是用greedy策略

找到的最佳解 S (存在某個決策不是greedy)

如何證明 ?

我們通常會假設一個不是用greedy策略

找到的最佳解 S (存在某個決策不是greedy)

接著我們將 S 的某個不是greedy的決策

換成 greedy

發現經過這次交換後不會更差

證明以下題目可以通過每次取最大得到答案 :

有 1、5、10元三種面額的硬幣

我要如何使用最少的硬幣來組出 x 元 ?

證明以下題目可以通過每次取最大得到答案 :

有 1、5、10元三種面額的硬幣

我要如何使用最少的硬幣來組出 x 元 ?

假設有一個不是用這個策略得到的解 S

S 中選10元硬幣的數量 < greedy 解中的數量

證明以下題目可以通過每次取最大得到答案 :

有 1、5、10元三種面額的硬幣

我要如何使用最少的硬幣來組出 x 元 ?

假設有一個不是用這個策略得到的解 S

S 中選10元硬幣的數量 < greedy 解中的數量

那麼我們一定可以將一些S解中

所選的 1元、5元湊一湊,換成10元

證明以下題目可以通過每次取最大得到答案 :

有 1、5、10元三種面額的硬幣

我要如何使用最少的硬幣來組出 x 元 ?

假設有一個不是用這個策略得到的解 S

S 中選10元硬幣的數量 < greedy 解中的數量

那麼我們一定可以將一些S解中

所選的 1元、5元湊一湊,換成10元

並且這樣所花費的硬幣數量一定不會更多

可以拿剛剛的反例 1、5、7元的來試試

可以拿剛剛的反例 1、5、7元的來試試

那麼我們一定可以將一些S解中

所選的 1元、5元湊一湊,換成7元

發現剛剛的這步不一定會成立

更多貪心例題

有n個活動,每個活動開始/結束時間分別為 a、b

現在要選出最多的活動,使得任兩活動時間不衝突

ex :

要用什麼策略greedy的選呢?

要用什麼策略greedy的選呢?

優先選擇目前可參加的活動中

結束時間最早的 !

假設有一個不是貪心法的最優解 S

(存在某個選擇不是結束時間最早的)

假設有一個不是貪心法的最優解 S

(存在某個選擇不是結束時間最早的)

那麼我們可以將那個選擇換成

當時結束時間最早的活動

假設有一個不是貪心法的最優解 S

(存在某個選擇不是結束時間最早的)

那麼我們可以將那個選擇換成

當時結束時間最早的活動

這樣不僅參加數量步減少,還會騰出更多空間給後面的活動

這樣不僅參加數量步減少,還會騰出更多空間給後面的活動

這樣不僅參加數量步減少,還會騰出更多空間給後面的活動

#include<bits/stdc++.h>
using namespace std;
int n,as,r;
struct movie{
    int s,t;
};
bool comp(movie a,movie b){
    return b.t > a.t;
}
vector<movie> p;
int main(){
    cin>>n;
    for(int i=0;i<n;++i){
        int a,b; cin>>a>>b;
        p.push_back({a,b});
    }
    sort(p.begin(),p.end(),comp);
    as =1;
    r = p[0].t;
    for(int i=1;i<n;++i){
        if(r>p[i].s)continue;
        r = p[i].t;
        as++;
    }
    cout<<as;
    return 0;
}

有n件工作要完成,每件工作的所需間為 t_i

,懲罰指數為 s_i,假設這件工作在單位時間 f 完成,

則會有懲罰  s_i * f

找到最小的懲罰總和

有n件工作要完成,每件工作的所需間為 t_i

,懲罰指數為 s_i,假設這件工作在單位時間 f 完成,

則會有懲罰  s_i * f

找到最小的懲罰總和

有四件工作,花費時間分別是 t=(1,4,5,6),而懲罰指數是 s=(1,3,4,7)。

 

EX :

有四件工作,花費時間分別是 t=(1,4,5,6),而懲罰指數是 s=(1,3,4,7)。

 

如果依編號順序(1,2,3,4),完工時間是(1,5,10,16),

扣款總額是

1*1 + 5*3 + 10*4 + 16*7 = 168。

EX :

有四件工作,花費時間分別是 t=(1,4,5,6),而懲罰指數是 s=(1,3,4,7)。

 

如果依編號順序(1,2,3,4),完工時間是(1,5,10,16),

扣款總額是

1*1 + 5*3 + 10*4 + 16*7 = 168。

如果依編號順序(4,1,3,2) ,完工時間是(6,7,12,16),

扣款總額是

6*7 + 7*1 + 12*4 + 16*3 = 145。

EX :

我們可以想想看要如何安排處理工作的排列順序

來讓懲罰最小

假設有一個不是greedy的最佳解排列 S

S_i 代表第 i 個是做哪個工作

假設有一個不是greedy的最佳解排列 S

S_i 代表第 i 個是做哪個工作

我們觀察相鄰兩項工作 a、b,

令 T 為之前所花費的總時間

並考慮兩種情況 :

 

1 : 不交換  (a -> b)

2 : 交換      (b -> a)

不交換 (先做a) 所產生的懲罰

(T+a_t)a_s + (T+a_t+b_t)b_s
= Ta_s+a_ta_s+Tb_s+a_tb_s+b_tb_s

不交換 (先做a) 所產生的懲罰

(T+a_t)a_s + (T+a_t+b_t)b_s
= Ta_s+a_ta_s+Tb_s+a_tb_s+b_tb_s

交換 (先做b) 所產生的懲罰

(T+b_t)b_s + (T+a_t+b_t)a_s
=Tb_s+b_tb_s+Ta_s+a_ta_s+b_ta_s

不交換 (先做a) 所產生的懲罰

(T+a_t)a_s + (T+a_t+b_t)b_s
= \cancel{Ta_s}+\cancel{a_ta_s}+\cancel{Tb_s}+a_tb_s+\cancel{b_tb_s}

交換 (先做b) 所產生的懲罰

(T+b_t)b_s + (T+a_t+b_t)a_s
=\cancel{Tb_s}+\cancel{b_tb_s}+\cancel{Ta_s}+\cancel{a_ta_s}+b_ta_s

不交換 (先做a) 所產生的懲罰

(T+a_t)a_s + (T+a_t+b_t)b_s
= \cancel{Ta_s}+\cancel{a_ta_s}+\cancel{Tb_s}+a_tb_s+\cancel{b_tb_s}

交換 (先做b) 所產生的懲罰

(T+b_t)b_s + (T+a_t+b_t)a_s
=\cancel{Tb_s}+\cancel{b_tb_s}+\cancel{Ta_s}+\cancel{a_ta_s}+b_ta_s

因此只要比較

a_tb_s 、 b_ta_s

誰較小即可

給定多組測資,每組包含 NNN 個人,每個人有兩個數值:

  • CiC_iCi:第 iii 人的餐點烹煮時間

  • EiE_iEi:第 iii 人的用餐時間

所有餐點需依序單獨烹煮,煮好立即上桌並開始用餐。每個人吃完的時間為他餐點被煮好時的時間 + EiE_iEi。你要決定煮餐順序

使得所有人都吃完的時間最早

要什麼東西greedy呢 ?

要什麼東西greedy呢 ?

發現其實烹飪時間多久其實不太重要?

因此把吃飯時間越長的排越前面呢 ?

假設有一個最佳解的排列 S

裡面包含了某相鄰兩項 a , b

不符合greedy

即 b 的吃飯時間比 a 快

假設有一個最佳解的排列 S

裡面包含了某相鄰兩項 a , b

不符合greedy

即 b 的吃飯時間比 a 快

假設烹飪時間為 a_c、b_c

吃飯時間為 a_e、b_e

之前所花費的烹飪時間總和為 T

假設有一個最佳解的排列 S

裡面包含了某相鄰兩項 a , b

不符合greedy

即 b 的吃飯時間比 a 快

假設烹飪時間為 a_c、b_c

吃飯時間為 a_e、b_e

之前所花費的烹飪時間總和為 T

考慮交換、不交換

假設烹飪時間為 a_c、b_c

吃飯時間為 a_e、b_e

不交換 :

a 吃完的時間 :

T+a_c+a_e
T+a_c+b_c+b_e

b 吃完的時間 :

交換 :

a 吃完的時間 :

T+b_c+b_e
T+b_c+a_c+a_e

b 吃完的時間 :

不交換 :

a 吃完的時間 :

T+a_c+a_e
T+a_c+b_c+b_e

b 吃完的時間 :

交換 :

a 吃完的時間 :

T+b_c+b_e
T+b_c+a_c+a_e

b 吃完的時間 :

如果 

a_e > b_e

不交換 :

a 吃完的時間 :

T+a_c+a_e
T+a_c+b_c+b_e

b 吃完的時間 :

交換 :

a 吃完的時間 :

T+b_c+b_e
T+b_c+a_c+a_e

b 吃完的時間 :

如果 

a_e > b_e

則上面打勾的式子會是最大的,

代表不交換較好

不交換 :

a 吃完的時間 :

T+a_c+a_e
T+a_c+b_c+b_e

b 吃完的時間 :

交換 :

a 吃完的時間 :

T+b_c+b_e
T+b_c+a_c+a_e

b 吃完的時間 :

如果 

a_e > b_e

則上面打勾的式子會是最大的,

代表不交換較好

因此我們將吃飯速度越慢的排越前面是好的

#include<bits/stdc++.h>
using namespace std;
#define maxn 10004
int n;
pair<int,int> p[maxn];

bool comp(pair<int,int> a,pair<int,int> b){
    return a.second > b.second;
}

main(){
    while(cin>>n && n){
        for(int i=1;i<=n;++i) cin>>p[i].first>>p[i].second;
        sort(p+1,p+1+n,comp);
        int as = 0,t = 0;
        for(int i=1;i<=n;++i){
            t += p[i].first;
            as = max(as,t+p[i].second);
        }
        cout<<as<<endl;
    }
}

總結

先靠著直覺、經驗,想想看

有沒有可以greedy的地方

總結

先靠著直覺、經驗,想想看

有沒有可以greedy的地方

接下來可以通過舉反例,或者上面示範的證明方法試著證明正確性

或者直接靠感覺

Made with Slides.com