贪心策略-避免证明(左神)

现给你许多串,要求你把他们连接成最小的字符串并返回。

最小串?这是贪心赤果果的暗示。

贪心策略:

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;
}

猜你喜欢

转载自blog.csdn.net/qq_39304630/article/details/82313229