P3045 【USACO12FEB】 Cow Coupons G
P3045 USACO12FEB Cow Coupons G - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
提供两种解法,反悔贪心和 wqs 二分套二分。
根据题意我们需要求出选 \([1, n]\) 个牛的最小价值,且花费券不超过 \(k\)。一个明显的贪心是有券1
肯定用券(\(K\le N\))。所以花费券一定是 \(k\) 张。
反悔贪心
很容易想到 DP 或贪心,普通 DP 看范围肯定是不行了,尝试贪心。
正常的贪心是先用优惠券买,再原价买。当然这比较容易推出是错的。如:
2 1 4
2 1
10 2
根据流程会选 \(1,10\),得出只能买一头牛,然而更优的是选两个 \(2\),可以买两头牛。
所以优惠券的优惠力度没有利用完全。考虑反悔,采用替代的方式让这个券给别的牛用。但这样没有券的消耗,那么我们先用完 k 张券,即先用券卖完,易知这些肯定是当前最小的。对于后 \(n - k\) 个因为已经没券了,所以要么直接买,要么替换券买(即 \(c[i]+p[j]-c[j]\),\(j\) 为被替换的牛)。用三个堆分别维护 \(p[i],c[i],p[i]-c[i]\) 即可。时间复杂度 \(O(n\log n)\).
在过程中判断最小值是否大于 \(m\) 如果大于说明已经买不了第 \(i\) 个牛了。输出 \(i - 1\)。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#define x first
#define y second
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
const int N = 50010;
LL n, m, k;
int p[N], c[N];
int st[N];
priority_queue<PII, vector<PII>, greater<PII>> q, d, q2;
int main()
{
cin >> n >> k >> m;
for (int i = 1; i <= n; i ++ )
{
scanf("%d%d", &p[i], &c[i]);
q.push({c[i], i});
q2.push({p[i], i});
}
LL ans = 0;
for (int i = 1; i <= k; i ++ )
{
PII t = q.top();
q.pop();
ans += t.x;
st[t.y] = 2;
d.push({p[t.y] - c[t.y], t.y});
if (ans > m)
{
cout << i - 1 << endl;
return 0;
}
}
for (int i = k + 1; i <= n; i ++ )
{
while (st[q2.top().y]) q2.pop();
while (st[q.top().y]) q.pop();
while (st[d.top().y] != 2) d.pop();
if (q2.top().x >= q.top().x + d.top().x)
{
PII t = q.top();
ans += t.x + d.top().x;
st[t.y] = 2;
st[d.top().y] = 1;
q.pop();
d.pop();
d.push({p[t.y] - c[t.y], t.y});
}
else
{
PII t = q2.top();
ans += t.x;
st[t.y] = 1;
q2.pop();
}
if (ans > m)
{
cout << i - 1 << endl;
return 0;
}
}
cout << n << endl;
return 0;
}
wqs 二分套二分
这种方法比较稳,也好想。对于能买多少牛,可以二分验证答案,即把问题转化为:买 \(x\) 头牛在恰好花费 \(k\) 张券下最小花费为多少。 这就是一个很典型的 wqs 二分问题了。
对于 check 函数是可以用贪心优化成 \(O(n)\)(这点请自己想)。二分加 wqs 二分是 \(O(\log n\log v),\max v = 10^9\),总体时间复杂度 \(O(n\log n \log v)\)。可以通过。
实际上效率比反悔贪心差不了太多(\(n\) 比较小)。
#include <iostream>
#include <cstring>
#include <algorithm>
#define x first
#define y second
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
const int N = 50010;
LL n, m, k;
LL ans, cnt;
bool st[N];
PII p[N], c[N];
struct Node
{
int p, c;
bool operator<(const Node &W)const
{
return c < W.c;
}
}g[N];
void check1(int x, int n)
{
ans = cnt = 0;
int t1 = 1, t2 = 1;
memset(st, 0, sizeof st);
for (int i = 1; i <= n; i ++ )
{
while (st[c[t2].y]) t2 ++ ;
while (st[p[t1].y]) t1 ++ ;
if (p[t1].x >= c[t2].x - x)
{
cnt ++ ;
st[c[t2].y] = true;
ans += c[t2 ++ ].x - x;
}
else
{
st[p[t1].y] = true;
ans += p[t1 ++ ].x;
}
}
}
bool check(int x)
{
int l = -(1e9 + 10), r = 0;
while (l < r)
{
int mid = l + r >> 1;
check1(mid, x);
if (cnt >= k) r = mid;
else l = mid + 1;
}
check1(l, x);
// cout << ans + l * k << endl;
return ans + l * k <= m;
}
int main()
{
cin >> n >> k >> m;
for (int i = 1; i <= n; i ++ )
{
int a, b;
scanf("%d%d", &a, &b);
p[i] = {a, i};
c[i] = {b, i};
}
sort(p + 1, p + 1 + n);
sort(c + 1, c + 1 + n);
int l = 0, r = n;
while (l < r)
{
int mid = l + r + 1 >> 1;
if (check(mid)) l = mid;
else r = mid - 1;
}
cout << l << endl;
return 0;
}