HDU 6438 Buy and Resell
题目
给 n 个城市物品价格,每个地方的物品价格不一样。低买高卖就可以赚差价。
从1 ~ n,每经过一个城市可以选择以当前城市价格买进一个商品,也可以以当前城市价格卖掉手中的一个商品。还可以选择什么都不干直接走。问最后能赚的最大利润是多少。
分析
贪心,不过思路很巧妙。
ans 代表最后利润,cnt 代表操作数,这里标记买进操作,最后输出 2 * cnt 即可。
首先用优先队列存我们已经买的物品价格,对于当前城市,如果队列中的最小值,也就是已经买的最便宜的值,小于当前物品价格,这样就造成了一个最大差值。这时我们完成了一笔交易。我们在结果加上。
但是有一个问题,这样得到的不是最优解。增加一个标记变量,标记每一笔交易中卖出的价格。完成这笔 A 交易后,将买进价格 x 出队,将卖出价格 y 入队,这样再后面的交易中,如果有城市价格跟 A 交易卖出价格 y 的差值最大。那么我们可以将这一笔交易反悔,以当前城市价格卖出。
相应的,A 交易卖出价格 y 等于成为了中转商品,为了使交易次数尽可能小,可以当作中转商品没有参加交易。这是标记变量就起了作用。
具体操作就是每完成一笔交易,判断交易的买进价格是否被标记过。(即是不是已经是一笔交易的卖出价格),如果是,那么标记清空,交易次数减一(cnt–;)。
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define INF 0x3f3f3f3f
#define fuck(x) cout<<x<<endl
const int N = 1e5 + 5;
const ll mod = 1e9 + 7;
int t, n;
ll ans, cnt;
int main(){
scanf("%d", &t);
while(t--){
scanf("%d", &n);
ans = cnt = 0;
map<int, int> vis; // 买卖一次 为 交易一次
priority_queue<int, vector<int>, greater<int>> q;
for(int i = 0, x; i < n; i++){
scanf("%d", &x);
q.push(x); // 假买
if(q.top() < x){ // 有值小于当前值,真卖 x
ans += x - q.top(); // 答案加上 利润
cnt++; // 交易数
if(vis[q.top()]){ // 如果 top 是前面某次交易的卖价
vis[q.top()]--; // 那么 top 就当作了中转
cnt--; // top 清除标记代表之后还可以交易
}
vis[x]++; // 标记 x 为交易的卖价
q.pop(); //
q.push(x); // 假如后面 x 被当作了中转,
// 那么添加两次 x ,是为了让后面还有 x 还有机会被当作交易的买价。
}
}
printf("%lld %lld\n", ans, cnt << 1);
}
return 0;
}
/*
*/