地址在这http://www.51nod.com/live/liveDescription.html#!liveId=18
pdf http://wwwwodddd.com/Greed.pdf
题目链接 https://www.51nod.com/contest/problemList.html#!contestId=54&randomCode=882897
(这是免费的,我和他们没有任何利益关系,只是这是一个很好的学习的机会)
A.低买高买
这道题需要反过来思考,这里的思路是先卖,然后根据需要再反悔, 有点类似网络流里面的反向弧的思想
首先假装已经有了股票,然后卖掉得到收益。
但是因为实际上没有,所以我们有两种操作,一种是把卖的改成持有, 一种是持有改成
买,这两种操作都可以让现在多一个股票,这个股票也就是刚才卖掉的股票
设股票价钱为x,则这两种操作都会使得当前收益少掉x
那么也就是说每一次我们都要先卖掉一支,然后执行两个操作中的一个来补回来
那么因为要钱最多,那么两个操作都是会让收益变少的,那么我们就要让操作所减少的收益最小。
所以我们可以把操作储存起来,然后优先队列排序,每次卖掉当前股票的同时执行最优的操作
最后输出答案就好
#include
#include
#define REP(i, a, b) for(int i = (a); i < (b); i++)
using namespace std;
int main()
{
priority_queue q;
int n, x, ans = 0;
scanf("%d", &n);
REP(i, 0, n)
{
scanf("%d", &x);
q.push(-x); q.push(-x); //负数为了实现小根堆
ans += x + q.top(); //这里q.top()是负数,两个操作都是减少x元
q.pop();
}
printf("%d\n", ans);
return 0;
}
B.排队接水
相信大家以前都做过,比较水。
两个思路。一个从宏观的角度来考虑,肯定是实际花的少的人在前面更优
第二个从微观的角度,相邻的两个人。设第一个人花时间a,第二个人花时间b
如果第一个人在前,那么总时间为b + 2a
如果第一个人在前,那么总时间为a + 2b
b + 2a与a + 2b, 统统减去a+b
变成a和b,也就是说谁时间短就排在前面更优
#include
#include
#define REP(i, a, b) for(int i = (a); i < (b); i++)
using namespace std;
const int MAXN = 1123;
int a[MAXN], n, ans;
int main()
{
scanf("%d", &n);
REP(i, 0, n) scanf("%d", &a[i]);
sort(a, a + n);
REP(i, 0, n) ans += (n - i) * a[i];
printf("%d\n", ans);
return 0;
}
C.接水问题2
这道题是上一题的升级版,这个时候不能用第一种思路了,因为还和重要性有关
要用第二种思路,即考虑相邻两个人的情况
设第一个人重要性a[x], 时间b[x], 同理第二个人a[y], b[y]
那么如果第一个人在前,则b[x] * a[x] + a[y] * (b[x] + b[y])
那么如果第二个人在前,则b[y] * a[y] + a[x] * (b[x] + b[y])
化简之后可得a[y] * b[x] 与 a[x] * b[y]
那么我们就比较这两个就好了
然后这里还要注意有个坑,有可能为0
时间为0,放在第一个,对答案没有贡献
重要性为0(心疼),放在最后一个,对答案没有贡献
所以直接在输入的时候忽略掉这组数据即可,即n--, i--(学到了)
最后注意开long long
#include
#include
#include
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define fi first
#define se second
using namespace std;
const int MAXN = 112345;
pair a[MAXN];
bool cmp(pair a, pair b)
{
return a.se * b.fi < a.fi * b.se;
}
int main()
{
int n;
scanf("%d", &n);
REP(i, 0, n)
{
scanf("%d%d", &a[i].fi, &a[i].se);
if(!a[i].fi || !a[i].se) i--, n--;
}
sort(a, a + n, cmp);
long long time = 0, ans = 0;
REP(i, 0, n)
{
time += a[i].se;
ans += time * a[i].fi;
}
printf("%lld\n", ans);
return 0;
}
D.做任务一
两个思路
(1)根据右端点排序,然后扫一遍,维护最后一个区间的右端点
每次新的区间看其左端的大不大于维护的右端点,能放就放
(2)根据左端点排序,同样能放就放,如果不能放的话试图使最后
一个区间的右端点更靠左,这样有利于后面的区间。
//右端点
#include
#include
#include
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define fi first
#define se second
using namespace std;
const int MAXN = 112345;
pair a[MAXN];
bool cmp(pair x, pair y)
{
return x.se < y.se || x.se == y.se && x.fi < y.fi;
}
int main()
{
int T, n, m;
scanf("%d", &T);
while(T--)
{
scanf("%d%d", &n, &m);
REP(i, 0, n) scanf("%d%d", &a[i].fi, &a[i].se);
sort(a, a + n, cmp);
int ans = 0, last = 0;
REP(i, 0, n)
if(a[i].fi >= last)
{
ans++;
last = a[i].se;
}
printf("%d\n", ans);
}
return 0;
}
//左端点
#include
#include
#include
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define fi first
#define se second
using namespace std;
const int MAXN = 112345;
pair a[MAXN];
int main()
{
int T, n, m;
scanf("%d", &T);
while(T--)
{
scanf("%d%d", &n, &m);
REP(i, 0, n) scanf("%d%d", &a[i].fi, &a[i].se);
sort(a, a + n);
int ans = 0, last = 0;
REP(i, 0, n)
{
if(a[i].fi >= last)
{
ans++;
last = a[i].se;
}
else if(a[i].se < last)
last = a[i].se;
}
printf("%d\n", ans);
}
return 0;
}
E.做任务三
依然两种做法
(1)按左端点排序,然后把结束时间加入优先队列。
每当有一个新任务的时候,看最早结束的人是否可以做,能做就做
不能做就加一个人。
其实仔细想想,当现在的任务有多个人能做的时候,无论谁做
都是最优的,不一定非要结束最早的能做,但是优先队列的作用
在于能不能至少有一个人能做,所以就看最早的那个。
(2)按照右端点排序,然后同样维护结束时间。
每当有一个新任务的时候,看最早结束的人是否可以做,如果能做
那么这里就要选结束最晚的那个人来做,因为要留下结束早的给
后面的任务。在这个思路里面如果有多个人能做,就要有抉择了,
和上一个思路不一样。同时因为这是要选择数据结构里面中间的
一个元素,所以我们用multiset。我是听了这个才知道有multiset
这个东西,以前都是用set的。
区别就是multiset允许有重复元素,set不允许,而这道题而言
结束时间是有可能一样的,是可以重复的,所以用multiset。
//左端点
#include
#include
#include
#include
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define fi first
#define se second
using namespace std;
const int MAXN = 112345;
pair a[MAXN];
int main()
{
int T, n, m;
scanf("%d", &T);
while(T--)
{
scanf("%d%d", &n, &m);
REP(i, 0, n) scanf("%d%d", &a[i].fi, &a[i].se);
sort(a, a + n);
priority_queue q;
int ans = 0;
REP(i, 0, n)
{
if(q.size() && -q.top() <= a[i].fi) q.pop();
else ans++;
q.push(-a[i].se);
}
printf("%d\n", ans);
}
return 0;
}
//右端点
#include
#include
#include
#include
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define fi first
#define se second
using namespace std;
const int MAXN = 112345;
pair a[MAXN];
int main()
{
int T, n, m;
scanf("%d", &T);
while(T--)
{
scanf("%d%d", &n, &m);
REP(i, 0, n) scanf("%d%d", &a[i].fi, &a[i].se);
sort(a, a + n);
multiset s;
int ans = 0;
REP(i, 0, n)
{
if(s.size() && *s.begin() <= a[i].fi)
s.erase(--s.upper_bound(a[i].fi))
else ans++;
s.insert(a[i].se);
}
printf("%d\n", ans);
}
return 0;
}
F.字符串连接
这道题不能直接用字典序,比如 b ba 按照字典序答案是bba,但是显然bab更优
所以这里用了一个很牛逼的比较方法,就是直接比较连接之后的大小
比较a + b与 b + a,这样的比较我还是头一次看到,牛逼。
#include
#include
#include
#include
#define REP(i, a, b) for(int i = (a); i < (b); i++)
using namespace std;
const int MAXN = 112;
string s[MAXN];
bool cmp(string a, string b)
{
return a + b < b + a;
}
int main()
{
int n;
scanf("%d", &n);
REP(i, 0, n) cin >> s[i];
sort(s, s + n, cmp);
REP(i, 0, n) cout << s[i];
cout << endl;
return 0;
}
G. 缓存交换
我纠结了好久好久为什么是删去下次最远的那个是最优的,但还不是非常清楚
我们看样例,3要进来的时候1和2选择,如果选择下一次近一些的1,下一次1要再进来的时候
会把2或3给踢出去,这样后面2和3进来就又要多一次
而选择较远的2的话,只需要花后来2进来的次数。真正比赛的时候就手算样例吧,然后凭着直觉选下一次最远的。
实现的时候记得下标从1开始,不然会出事,代码注释里面有写
#include
#include
#include
H.挑剔的美食家
思路很简单,就是每个奶牛选最便宜的草就好了。
实现的话要让品质从大到小排序,然后枚举奶牛,在集合中加入能选的草,然后选最便宜的。
排序是因为这样就品质而言, 前面的奶牛可以选的草后面的奶牛一定在品质上是满足的
可以说是节省了时间,然后再从价格上去挑选符合且最便宜的就可以了。
#include
#include
#include
#include
#include
#define fi first
#define se second
#define REP(i, a, b) for(int i = (a); i < (b); i++)
using namespace std;
const int MAXN = 112345;
pair a[MAXN], b[MAXN];
int n, m, p;
multiset s;
int main()
{
scanf("%d%d", &n, &m);
REP(i, 0, n) scanf("%d%d", &a[i].se, &a[i].fi), a[i].fi *= -1;
REP(i, 0, m) scanf("%d%d", &b[i].se, &b[i].fi), b[i].fi *= -1;
sort(a, a + n);
sort(b, b + m);
long long ans = 0;
REP(i, 0, n)
{
while(p < m && b[p].fi <= a[i].fi) s.insert(b[p++].se);
multiset::iterator it = s.lower_bound(a[i].se);
if(it == s.end()) { ans = -1; break; }
else { ans += *it; s.erase(it); }
}
printf("%lld\n", ans);
return 0;
}
I.最高的奖励
按照时间排序,然后维护一个价值从小到大的优先队列。
每一次不管能不能做,先加入,如果发现不能做的话,
那么就删除价值最小的即可
先做后来可以反悔。
#include
#include
#include
#include
#define fi first
#define se second
#define REP(i, a, b) for(int i = (a); i < (b); i++)
using namespace std;
const int MAXN = 51234;
pair a[MAXN];
int n;
int main()
{
scanf("%d", &n);
REP(i, 0, n) scanf("%d%d", &a[i].fi, &a[i].se);
sort(a, a + n);
long long ans = 0;
priority_queue q;
REP(i, 0, n)
{
ans += a[i].se;
q.push(-a[i].se);
if(q.size() > a[i].fi)
{
ans += q.top();
q.pop();
}
}
printf("%lld\n", ans);
return 0;
}
J.夹克老爷的逢三抽一
每次选择价值最高的m[i], 然后把m[i]改成m[i+1] + m[i-1] - m[i]
表示可以后悔,如果后悔的话就是不选m[i],选m[i+1]和m[i-1]
另外注意用链表
#include
#include
#include
#define fi first
#define se second
#define REP(i, a, b) for(int i = (a); i < (b); i++)
using namespace std;
typedef long long ll;
const int MAXN = 112345;
int L[MAXN], R[MAXN], n;
ll m[MAXN];
set > s;
void insert(int i) { s.insert(make_pair(m[i], i)); }
void erase(int i) { s.erase(make_pair(m[i], i)); }
void del(int i)
{
erase(i);
L[R[i]] = L[i];
R[L[i]] = R[i];
}
int main()
{
scanf("%d", &n);
REP(i, 0, n)
{
scanf("%lld", &m[i]);
insert(i);
L[(i + 1) % n] = i;
R[i] = (i + 1) % n;
}
ll ans = 0;
REP(i, 0, n / 3)
{
int j = (--s.end())->se;
ll a = m[L[j]], b = m[j], c = m[R[j]];
ans += b;
del(L[j]), del(R[j]);
erase(j);
m[j] = a + c - b;
insert(j);
}
printf("%lld\n", ans);
return 0;
}
总结:贪心一般是第一题和第二题,偏简单。比赛的时候可以手算样例然后看直觉来贪心。