题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6438
题意:一个奸商在倒卖石油,对于每个城市都有不同的石油的价格,当奸商按顺序到达某个城市之后,他可以有3种操作:
1.买一个石油
2.卖一个石油
3.什么都不做的走开
现在按顺序给出每个城市的石油价格,求最大获利,和最少的交易次数。
题解:第一眼看到这个问题,觉得是个DP,想了半天没想出来,后来看题解才知道,是个贪心。
怎么贪心呢?
我们要求获利最大,所以必定低价买入高价卖出。但是我们会考虑到一个问题,就是什么时候卖?换句话说就是可能当前卖出买入价格最小的石油,获利也许不是最大。如 1 3 10这个样例,很明显在1的价格位置买入,3的价格卖出,是可以赚钱的,但是不是最优解。所以我们需要思考怎么“后悔”。
我们很容易可以发现加入 在 A地方买入 B地方卖出,再在B地方买入,在C地方卖出。其实等价于 A地方买入 C地方卖出。用样例解释一波:
还是1 3 10这个样例。我们在1价格买入,3价格卖出,获利2.再以3价格买入,然后在10价格卖出,获利7.把获利加起来就是9,实际上等同于在1价格买入,在10价格卖出。
这个先卖出,再次买入就是我们的“后悔”操作。
具体实现如下:
对于当前城市的价格比我最小值小的,就意味着我们在这城市没有获利,我们低价买入。
对于当前城市的价格比我最小值大的,就意味着我们在这城市卖出会有获利,我们选择卖出买入的最小值。并且记录我们获利的多少。为了我们能后悔我们选择再次以当前价格买入。我们要买两次,为什么要买两次的。首先我们第一次表明我们是以当前价格卖出,若以后还能有更优值,就可以把这个卖出去。这就是卖出再买入然后在卖出的操作。对于这样的操作我们实际上只需要记录为一次交易即可,即最初的买和最后的卖。相当于我加了个跳板。那么第二次就表明。我们是以这个价格买入的。就可以在后面将他卖出。因为若便利到这个价格就以为,你之前的最小值已经在更优的位置被卖出去了,现在才是最小值。
所以实际上我们就是对手中持有的石油进行了分类。跳板or非跳板,卖出跳板时次数不变,卖出非跳板时次数+2.手中持有的石油用优先队列维护。
这个题还是蛮难理解的(也行是我太菜)希望这篇详细的博客能给大家启发。
代码如下:
#include<bits/stdc++.h>
using namespace std;
const int mx = 1e5+5;
struct node{
int val,type; // type表示分类 0 表示跳板, 2 表示非跳板
bool operator <(const node b) const{
if(val != b.val) return val > b.val; // 按照权值从小到大
return type > b.type; // 按照类型从小到大。
}
};
priority_queue<node> Q;
int main(){
int z;
scanf("%d",&z);
while(z--){
while(!Q.empty()) Q.pop();
int n,sum = 0;
long long ans = 0;
node temp ;
scanf("%d",&n);
for(int i = 1 ; i <= n ; i ++){
int val ;
scanf("%d",&val);
temp.val = val;
temp.type = 2; // (非跳板)买入就是类型2
if(!Q.empty() && val > Q.top().val){ // 若当前能赚钱就买出最小值
ans += val - Q.top().val; //统计获利
sum += Q.top().type; // 为什么type是0 和2呢 0表示无效交易,2表示有效交易 。
temp.type = 0;
Q.pop();
Q.push(temp); //加入两次,当中后悔药使用。
temp.type = 2;
Q.push(temp);
}
else
Q.push(temp);
}
printf("%lld %d\n",ans,sum);
}
return 0;
}