和舍友打了一个半小时嘴炮,终于搞定了这道题,很开心。本题思路就是构造一个答案a0,a1,a2...... ai是找零的硬币。而需要找零的总钱数就是a0+a1+a2......
贪心策略:
1.因为题目要求是获得最多种未曾获得的硬币,所以我们最开始的想法是将所有没获得的硬币都放进答案中。但是这样真的对吗?假如输入是1 0, 2 0, 3 0。那么找零为6时,1,2,3不会都用到。因为 1 + 2 >= 3,所以在经过3找零后,需要找零的钱<=2,所以1,2至多有一个数被用到。这启发我们假如已经构造了局部最优答案a,b。想要构造更大的答案a,b,c时。当a + b >= c,a,b,c三个数不可能同时用到。所以这个时候我们就要开始删除一个数了,咋删呢?看策略2。
2.假如删除c,因为a + b >= c所以在找零的时候必然会先被c找零,所以这种方法是错误的。那么我们就要从a,b中间删一个,根据题意要使得找零的钱数最少,所以我们删掉a,b中较大的那个b.
3.假如c硬币收藏家已经有了,从答案中删除c
hint:有人可能会想到这样一个问题,我们从答案a,b,c向a,b,c,d拓展时,假如a+b+c>=d,我们要删除c,那么会不会剩下的a+b仍然大于d呢?即我们要连续删除多个数直至前面的和小于后面。答案是否定的!!最多删除一个数。假如a+b+c>=d 且 a+b>=d。由于a,b,c是已经构造好的答案,所以还有a+b<c。我们得出c>a+b>=d,即c>d的荒谬结论,c面值肯定小于d,因为我们是按面值从小到大拓展的。
举个例子:输入是
7 25
1 0
2 0
3 1
5 0
10 0
13 0
20 0
先构造答案1 2
构造1 2 3
发现1 + 2 >= 3
删除2
又因为3已经有了
删除3
构造的答案就是1
再构造1 5
再构造1 5 10
因为1 + 5 < 10所以可用
再构造1 5 10 13
因为1 + 5 + 10 >= 13
所以删除10
因为13没有
所以答案是1 5 13
再构造 1 5 13 20
发现1 + 5 + 13 + 20 > k - 1超出限制
所以删掉20
即最后的答案就是 1 5 13
收藏家需要花的钱是k - (1 + 5 + 13) = 6
具体代码如下:
#include <algorithm>
#include <iostream>
#include <iterator>
#include <sstream>
#include <fstream>
#include <istream>
#include <ostream>
#include <complex>
#include <cstring>
#include <utility>
#include <cstdlib>
#include <cstdio>
#include <vector>
#include <string>
#include <cctype>
#include <ctime>
#include <cmath>
#include <queue>
#include <stack>
#include <list>
#include <new>
#include <set>
#include <map>
using namespace std;
const int maxn = 500000 + 5;
const int INF = 0x3f3f3f3f;
typedef long long int LL;
typedef vector<LL> vec;
vec ans;//保存答案
int main()
{
//freopen("1.txt", "r", stdin);
LL n, k;
scanf("%I64d%I64d", &n, &k);
LL sum = 0;
for (int i=0; i<n; i++)
{
LL p, has;
scanf("%I64d%I64d", &p, &has);
if (sum >= p)//要删去上次答案的最后一项
{
sum -= ans[ans.size() - 1];
ans.pop_back();
}
if (!has)//如果当前硬币收藏家没有,则加入答案中
{
if (sum + p > k - 1)
break;
ans.push_back(p);
sum += p;
}
}
if (sum == 0)//如果硬币收藏家全有,则找零的钱为1
sum = 1;
cout << ans.size() << endl << k - sum << endl;
return 0;
}