贪心入门——任务执行顺序问题

版权声明:转载请注明出处链接 https://blog.csdn.net/qq_43408238/article/details/88818407

先上题目,再上解析,enn,参考了一些大佬的文章,总感觉自己对题目的理解能力有点问题,说狠点,就是太弱了。

题目如下:

    有N个任务需要执行,第i个任务计算时占R[i]个空间,而后会释放一部分,最后储存计算结果需要占据O[i]个空间(O[i] < R[i])。

例如:执行需要5个空间,最后储存需要2个空间。给出N个任务执行和存储所需的空间,问执行所有任务最少需要多少空间。

输入

第1行:1个数N,表示任务的数量。(2 <= N <= 100000)
第2 - N + 1行:每行2个数R[i]和O[i],分别为执行所需的空间和存储所需的空间。(1 <= O[i] < R[i] <= 10000)

输出

输出执行所有任务所需要的最少空间。

输入示例

20
14 1
2 1
11 3
20 4
7 5
6 5
20 7
19 8
9 4
20 10
18 11
12 6
13 12
14 9
15 2
16 15
17 15
19 13
20 2
20 1


输出示例

135

先写点正常容易理解的(含借鉴别人的),再来段复制教程上的原话。

这个题目最重要的是确定贪心策略,每个任务执行需要R的空间,储存需要O的空间,他俩的差值不妨设为B,这个问题这一开始也不大理解,需要多少空间?应该怎样安排都差不多吧,其实不然。你可以自己随便写两三组数,随意按不同的顺序尝试下,结果是不一样的。为什么呢?因为他会释放空间,这又要考虑内存资源的占有问题,

假设所有的任务满足 R== O ,那么最优解就是O之和。然而事实是 R > O ,那么每次一执行某个任务,

就会先多调用 R - O的空间。如图:灰黑色条代表执行时占用的空间,黄色代表执行完后存储空间。

实际上,每次释放的R-O的大小(灰色部分),就是总是在多占用内存的部分,

最上面的红线就是最优解了(尽可能越往后,使得灰色部分越低,也会是差值B越小)。

所以我们以灰色部分按从大到小排序,就是最优策略。可以尽量降低红线的位置

总结:其实,明白整个过程,就会觉得这么简单啊,可以再深一步理解,如上图所示,最后答案就是差值B最小的执行时间R与其余储存内存O之和的和。

AC代码:

#include<iostream>
#include<vector>
#include<string>
#include<cstring>
#include<cmath>
#include <algorithm>
#include <stdlib.h>
#include <cstdio>
#include<sstream>
#include<cctype>
#include <set>
#include<queue>
#include <map>
#include <iomanip>
typedef long long ll;
using namespace std;
struct node
{
    ll x,y,z;
};
bool cmp(node a,node b)
{
    return a.z>b.z;
}
node ans[100000];
int main()
{
    ll t;
    scanf("%lld",&t);
    for(ll i=0;i<t;i++)
    {
        scanf("%lld%lld",&ans[i].x,&ans[i].y);ans[i].z=ans[i].x-ans[i].y;
    }
    sort(ans,ans+t,cmp);
    ll sum=0;
    for(ll i=0;i<t-1;i++)
    {
        sum+=ans[i].y;
    }
    sum+=ans[t-1].x;
    printf("%lld",sum);
}

还可这么写:

#include<iostream>
#include<vector>
#include<string>
#include<cstring>
#include<cmath>
#include <algorithm>
#include <stdlib.h>
#include <cstdio>
#include<sstream>
#include<cctype>
#include <set>
#include<queue>
#include <map>
#include <iomanip>
typedef long long ll;
#define  INF  0x3f3f3f3f
using namespace std;
struct node
{
    ll x,y,z;
};
bool cmp(node a,node b)
{
    return a.z<b.z;//定义比较规则,找最小的z
}
node ans[100000];
int main()
{

    ll t,sum=0;node a;
    scanf("%lld",&t);
    for(ll i=0;i<t;i++)
    {
        scanf("%lld%lld",&ans[i].x,&ans[i].y);sum+=ans[i].y;
        ans[i].z=ans[i].x-ans[i].y;
    }
      sum+=(*min_element(ans,ans+t,cmp)).z;

    printf("%lld",sum);
}

最后的最后,附上一些教程文字,如下:

分析: 本题可以抽象成,从一个整数开始,每次减去a,再加上b (a,b都是正数),要求每次操作都不产生负数。


针对本题a[i] = R[i], b[i] = R[i] – O[i],注意O[i] < R[i],我们有0<b[i]<a[i]。 所以尽管每次有减有加,但是加的没有减的多,总数还是在不断见效的。关键我们是要“最有利”的一种执行顺序。大家可以尝试多种贪心策略。


我们给出标准答案——按照b[i]不增的顺序排序,是最“有利”的。


为了定义“有利”,我们这样证明我们的结论:


如果对于b[0]>=b[1] >=…>=b[x] < b[x + 1] 
(a[0],b[0])….(a[x], b[x]) (a[x + 1], b[x + 1])的组合可以不产生负数,则我们交换b[x]和b[x + 1]也可以不产生负数。


证明:
 

交换(a[x], b[x])和(a[x + 1], b[x + 1])对x + 1更有利了,因为每个括号实际上是一个负数,所以越早安排这个括号,被减数就越大,就越不容易形成负数。
关键看(a[x],b[x])移动到后面会不会产生负数。


那其实是看之前的结果 -a[x + 1] + b[x + 1] – a[x]会不会产生负数,(注意-a[x + 1] + b[x + 1]不会产生负数,因为我们刚才已经证明了,对x + 1更有利)


而我们知道之前的结果-a[x] + b[x] – a[x + 1]不会产生负数(因为我们的假设就是这样),而b[x + 1] > b[x],所以前者更大,所以-a[x + 1] + b[x + 1] – a[x]不会产生负数。
 

因此我们证明了交换之后仍然不产生负数,也就是原先不产生负数,我们交换后仍然不产生负数。

而经过若干次这样的交换之后,我们肯定会把序列交换成按照b的不增顺序排序的。从而我们证明了,任何可行的方案都不好于按照b不增顺序排序的序列执行的方案,从而证明了我们的贪心策略是有效的。

很奇怪的策略——我们只考虑了b,居然能得到最优策略。可见贪心算法还是需要感觉,大胆假设,小心求证。

猜你喜欢

转载自blog.csdn.net/qq_43408238/article/details/88818407