1. 程式人生 > >貪心策略-避免證明(左神)

貪心策略-避免證明(左神)

現給你許多串,要求你把他們連線成最小的字串並返回。

最小串?這是貪心赤果果的暗示。

貪心策略:

A.把串按照字典序升序排序後,直接拼接

分析:對於貪心策略最直接的否定方法就是隨便找一個反例,"ba"、"b",按照字典序排序後的拼接結果是"bba",但是最小的串是"bab",因此該貪心策略有誤

B.如果str1+str2<=str2+str1,那麼排序時應該str1、str2,否則str2、str1。

分析:我們可以將這些串想象成數串,要求他們拼接後的數字結果最小。【字母串想象成26進位制就行】設現有串a,b,c,

ab=a*m(b)+b【m(b)表示的是在某一進位制下,a所對應的權重,舉個栗子:1213=12*m(13)+13=12*10^2+12】

ac=a*m(c)+c

bc=b*m(c)+c

現要證若ab<=ba,bc<=cb,則ac<=ca。也就是如果排序後a在b前,b在c前,則a在c前【傳遞性證明】

ab<=ba -> a*m(b)+b<=b*m(a)+a .....(1)

bc<=cb -> b*m(c)+c<=c*m(b)+b ......(2)

對1式兩邊-b*c得到:a*m(b)*c<=(b*m(a)+a-b)*c  ......(1')

對2式兩邊-b*a得到:(b*m(c)+c-b)*a<=c*m(b)*a   ......(2')

因為兩個等式共有a*m(b)*c,所以可以得到b*m(a)*c+a*c-b*c>=b*m(c)*a+a*c-b*a,移項後兩邊同時/b,就得到了c*m(a)+a>=a*m(c)+c,即ac<=ca。

我們假設按照這種策略排序後連線的字串為:

……a m1 m2 m3 …… b……

我們依次將b向前交換

……a m1 m2 b …… m3 ……

……a m1 b m2 …… m3 ……

……a b m1 m2 …… m3 ……

……b a m1 m2 …… m3 ……

由於第一步是排序後的連線結果,所以我們可以得到m3b<=bm3,所以當m3和b交換後,無疑是把這個串變大了,同理,往下的每一步,串都在變大。因此,a和b不能交換。同時也得證該策略的正確性。

左神忠告:貪心策略更多的講究感覺,當不確定貪心策略的正確性時,寫個對數器驗證,往往都比證明要節省時間。

程式碼:

#include<iostream>
#include<algorithm>
#include<string>
using namespace std;
string STORE[105];
bool cmp(string str1,string str2){
    return str1+str2<=str2+str1;
}

int main(){
    int n;
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>STORE[i];
    }
    sort(STORE+1,STORE+1+n,cmp);
    for(int i=1;i<=n;i++)cout<<STORE[i];
    return 0;

 

例子:現有一批活動,給定了開始活動的時間和結束活動的時間,現問最多可以舉辦多少場活動?

貪心策略:

A.選擇時長較短的活動舉辦,由於佔用時間短,和其他時間衝突的可能性就小。

反例:

A、B雖然時長比較大,但是選擇C會導致A、B都不能執行,得不償失。

 

B.選擇開始最早的舉辦,由於開始的早,因此每次重新考慮要舉辦的活動時,第一個活動會更有可能為後面的活動爭取到更多的時間。

反例:

C開始的早,但是選擇C會導致A和B不能選擇。

 

C.選擇最早結束的活動先舉辦,由於結束的早,因此每次重新考慮要舉辦的活動時,會更有可能為後面的活動爭取到更多的時間。

程式碼:

#include<cstdio>
#include<algorithm>
using namespace std;
struct TiMe {
    int start, over;
};
bool cmp(TiMe x, TiMe y) {
    return x.over < y.over;
}
int total;
TiMe activity[105];
int main() {
    int n,clock=-1;
    scanf("%d", &n);
    for (int i = 1;i <= n;i++) {
        scanf("%d%d",&activity[i].start,&activity[i].over);
    }
    sort(activity + 1, activity + 1 + n, cmp);
    for (int j = 1;j <= n;j++) {
        if (clock <= activity[j].start) {
            clock = activity[j].over;
            total++;
        }
    }
    printf("可執行活動最多%d個",total);
    return 0;
}

 

例子:有一批專案,現給出每個專案需要資金和純利潤(利潤不存在小於0的情況),以及最多可選擇k個專案做和啟動資金,現在要求你給出最大利潤。

貪心策略:在可選擇的專案中選擇可獲利最多的專案做

簡要分析可行性:因為題中說了是純利潤,而且不會小於0,所以只要做專案了,連本帶利收回資金後,我們的啟動資金是一定會變多的,因此啟動資金滿足非遞減的性質。啟動資金變多了,就會使得可選擇的專案變多,這樣更有可能獲得更大利潤。

實現:1.我們要在所有專案中從投入資金小的開始挑選出所有可選專案

           2.我們要在所有可選專案中得到最大利潤對應的專案

同時,由於我們的啟動資金是非遞減的,因此在i時刻已經可選的專案在i+1時刻一定可選,沒必要再查詢一次。

前者滿足小根堆,後者滿足大根堆,因此維護兩個堆即可。

#include<cstdio>
#include<algorithm>
#include<queue>
#include<vector>
#include<functional>
using namespace std;
struct Node {
    int need, profit;
    bool operator <(Node x) {
        return need < x.need;
    }
    bool operator>(Node x) {
        return profit > x.profit;
    }
};

Node Math[105];
int total;

int main() {
    int n, start,k,total=0,cur=1;
    priority_queue<Node> big;
    priority_queue<Node, vector<Node>, greater<Node>() > small;
    scanf("%d%d%d", &n, &start,&k);
    for (int i = 1;i <= n;i++) {
        scanf("%d%d", &Math[i].need, &Math[i].profit);
    }
    for (int i = 1;i <= n;i++) {
        small.push(Math[i]);
    }
    while (total < k) {
        while (!small.empty() && small.top().need<start) {
            big.push(small.top());
            small.pop();
        }
        while (!big.empty()) {
            start += big.top().profit;
            big.pop();
        }
        total++;
    }
    printf("最大利潤%d", start);
    return 0;
}